diff --git a/CHANGELOG.md b/CHANGELOG.md index 194650103f508..e8f1037491cc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +### Bug Fixes + +- **api-gateway:** Make pre-agg build job status per-entry and data-source aware ([#11666](https://github.com/cube-js/cube/issues/11666)) ([694955f](https://github.com/cube-js/cube/commit/694955f864375515d84d463e9e7ee89a156a4a2b)) +- **query-orchestrator:** initialize missing data source queue in isPartitionExist, thanks [@dochernyshov](https://github.com/dochernyshov) ([#11617](https://github.com/cube-js/cube/issues/11617)) ([ab48039](https://github.com/cube-js/cube/commit/ab48039f1a3d46d890367b0fc898d9ebf1322786)) +- **server-core:** release orchestrators evicted from the LRU ([#11661](https://github.com/cube-js/cube/issues/11661)) ([b82c58a](https://github.com/cube-js/cube/commit/b82c58aa245919a36e3d20f79105b639d0c16863)) + +### Features + +- **cubestore:** cap concurrent websocket connections per user ([#11667](https://github.com/cube-js/cube/issues/11667)) ([111eb3e](https://github.com/cube-js/cube/commit/111eb3ec049b9be1fa2f31186a7f83f58b5a0291)) + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) ### Bug Fixes diff --git a/docs-mintlify/admin/connect-to-data/multiple-data-sources.mdx b/docs-mintlify/admin/connect-to-data/multiple-data-sources.mdx index 89f66bc334857..42d56014832c1 100644 --- a/docs-mintlify/admin/connect-to-data/multiple-data-sources.mdx +++ b/docs-mintlify/admin/connect-to-data/multiple-data-sources.mdx @@ -64,6 +64,10 @@ Cubes that don't set `data_source` use the default source — there's no need to write `data_source: default` explicitly, though you can if you prefer to be explicit. +A single query can also span sources: see [querying across data +sources](/recipes/data-modeling/cross-data-source-queries) for appending rows +from cubes in different databases into one result set. + For multitenancy scenarios where the data source is selected dynamically per request, see [`driver_factory`](/reference/configuration/config#driver_factory) and the [multitenancy guide][ref-config-multitenancy]. diff --git a/docs-mintlify/api-reference/api.yaml b/docs-mintlify/api-reference/api.yaml index f7b62151099cc..ef1d9aa05230e 100644 --- a/docs-mintlify/api-reference/api.yaml +++ b/docs-mintlify/api-reference/api.yaml @@ -105,19 +105,14 @@ paths: type: integer minimum: 0 - in: query - name: first - required: false + name: after schema: - type: integer - minimum: 1 - description: Page size for cursor-based pagination + type: string - in: query - name: after - required: false + name: first schema: minimum: 1 - type: string - description: Cursor for fetching the next page + type: integer responses: '200': content: @@ -272,6 +267,45 @@ paths: tags: - Deployments /api/v1/deployments/{deploymentId}/dbt-sync: + get: + operationId: listDbtSyncs + parameters: + - in: path + name: deploymentId + required: true + schema: + type: integer + - in: query + name: status + schema: + oneOf: + - $ref: '#/components/schemas/DbtSyncRunsQueryStatus' + - type: 'null' + - in: query + name: trigger + schema: + oneOf: + - $ref: '#/components/schemas/DbtSyncRunsQueryTrigger' + - type: 'null' + - in: query + name: after + schema: + type: string + - in: query + name: first + schema: + minimum: 1 + type: integer + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DbtSyncRunListResponse' + description: '' + summary: List dbt syncs for a deployment + tags: + - dbt Sync post: operationId: startDbtSync parameters: @@ -344,6 +378,30 @@ paths: summary: Get the status of a dbt sync tags: - dbt Sync + /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}/logs: + get: + operationId: getDbtSyncLogs + parameters: + - in: path + name: deploymentId + required: true + schema: + type: integer + - in: path + name: syncJobId + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DbtSyncLogEntriesListResponse' + description: '' + summary: Get the log of a dbt sync + tags: + - dbt Sync /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}/result: get: operationId: getDbtSyncResult @@ -377,6 +435,19 @@ paths: required: true schema: type: integer + - in: query + name: after + schema: + oneOf: + - type: string + - type: 'null' + - in: query + name: first + schema: + oneOf: + - minimum: 1 + type: integer + - type: 'null' responses: '200': content: @@ -389,8 +460,9 @@ paths: - Env Variables x-mint: content: >- - Lists the deployment environment variables. Values of secret-named variables (containing - PASS, SECRET, TOKEN, KEY, or CREDENTIAL) are returned as `[ENCRYPTED]`. + Lists the deployment environment variables, cursor-paginated via `first`/`after`. Values + of secret-named variables (containing PASS, SECRET, TOKEN, KEY, or CREDENTIAL) are + returned as `[ENCRYPTED]`. put: operationId: setEnvVariables parameters: @@ -464,6 +536,15 @@ paths: - type: 'null' type: integer minimum: 0 + - in: query + name: after + schema: + type: string + - in: query + name: first + schema: + minimum: 1 + type: integer responses: '200': content: @@ -508,6 +589,15 @@ paths: - type: 'null' type: integer minimum: 0 + - in: query + name: after + schema: + type: string + - in: query + name: first + schema: + minimum: 1 + type: integer responses: '200': content: @@ -822,6 +912,15 @@ paths: oneOf: - $ref: '#/components/schemas/DeploymentLogsQueryContainer' - type: 'null' + - in: query + name: after + schema: + type: string + - in: query + name: first + schema: + minimum: 1 + type: integer responses: '200': content: @@ -847,7 +946,6 @@ paths: description: Numeric id of the deployment that owns the notification. - in: query name: dashboardId - required: false schema: oneOf: - minimum: 1 @@ -856,14 +954,8 @@ paths: Optional filter: only return notifications for this numeric dashboard id. Provide this OR dashboardPublicId, not both. - type: 'null' - type: integer - minimum: 1 - description: >- - Optional filter: only return notifications for this numeric dashboard id. Provide this - OR `dashboardPublicId`, not both. - in: query name: dashboardPublicId - required: false schema: oneOf: - type: string @@ -871,13 +963,8 @@ paths: Optional filter: only return notifications for this dashboard public id. Provide this OR dashboardId, not both. - type: 'null' - type: string - description: >- - Optional filter: only return notifications for this dashboard public id. Provide this OR - `dashboardId`, not both. - in: query name: recipientUserId - required: false schema: oneOf: - minimum: 1 @@ -886,14 +973,8 @@ paths: Optional filter: only return notifications received by this user, by user id. Mutually exclusive with recipientEmail and the embed-user filter. - type: 'null' - type: integer - minimum: 1 - description: >- - Optional filter: only return notifications received by this user, by user id. Mutually - exclusive with `recipientEmail` and the embed-user filter. - in: query name: recipientEmail - required: false schema: oneOf: - type: string @@ -901,13 +982,8 @@ paths: Optional filter: only return notifications received by this user, by email. Mutually exclusive with recipientUserId and the embed-user filter. - type: 'null' - type: string - description: >- - Optional filter: only return notifications received by this user, by email. Mutually - exclusive with `recipientUserId` and the embed-user filter. - in: query name: recipientEmbedTenantName - required: false schema: oneOf: - type: string @@ -915,13 +991,8 @@ paths: Optional filter: only return notifications received by this embed user. Required together with recipientExternalId; mutually exclusive with the user filters. - type: 'null' - type: string - description: >- - Optional filter: only return notifications received by this embed user. Required - together with `recipientExternalId`; mutually exclusive with the user filters. - in: query name: recipientExternalId - required: false schema: oneOf: - type: string @@ -929,25 +1000,15 @@ paths: Optional filter: only return notifications received by this embed user. Required together with recipientEmbedTenantName; mutually exclusive with the user filters. - type: 'null' - type: string - description: >- - Optional filter: only return notifications received by this embed user. Required - together with `recipientEmbedTenantName`; mutually exclusive with the user filters. - in: query - name: first - required: false + name: after schema: - type: integer - minimum: 1 - maximum: 200 - description: Page size for cursor pagination (default 100, max 200). + type: string - in: query - name: after - required: false + name: first schema: minimum: 1 - type: string - description: Opaque cursor for the next page; pass the previous response's `pageInfo.endCursor`. + type: integer responses: '200': content: @@ -1184,26 +1245,18 @@ paths: type: integer description: Numeric id of the notification (scheduled run). - in: query - name: first - required: false + name: after schema: oneOf: - type: string - type: 'null' - type: integer - minimum: 1 - maximum: 200 - description: Page size for cursor pagination (default 100, max 200). - in: query - name: after - required: false + name: first schema: oneOf: - minimum: 1 type: integer - type: 'null' - type: string - description: Opaque cursor for the next page; pass the previous response's `pageInfo.endCursor`. responses: '200': content: @@ -1283,6 +1336,19 @@ paths: required: true schema: type: integer + - in: query + name: after + schema: + oneOf: + - type: string + - type: 'null' + - in: query + name: first + schema: + oneOf: + - minimum: 1 + type: integer + - type: 'null' responses: '200': content: @@ -1597,6 +1663,64 @@ paths: Requires **edit** access to the report. /api/v1/deployments/{deploymentId}/reports/{reportId}/connect-workbook: + delete: + operationId: disconnectReportFromWorkbook + parameters: + - in: path + name: deploymentId + required: true + schema: + type: number + - in: path + name: reportId + required: true + schema: + type: number + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DisconnectReportFromWorkbookInput' + description: DisconnectReportFromWorkbookInput + required: false + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Report' + description: '' + summary: Disconnect a report from one spot in a spreadsheet + tags: + - Reports + x-mint: + content: |- + Remove a single placement of a report — one sheet + anchor in one + spreadsheet — leaving every other placement, in this workbook and in others, alone. + + Address the placement by `placementId`, or (for placements recorded before ids + existed) by `externalWorkbookId` plus `sheetName` and `anchorCell` — or a + `resultLocation` naming both. Neither the sheet name nor the anchor is optional in + that form: matching is by sheet + anchor, and a target missing either addresses + nothing. + + `sheetId` is an optional extra, not a substitute for `sheetName`: a placement + written before that field existed has only a name, and no id the server could + resolve to it, so an id-only address would match nothing while looking correct. + + Send a `sheetId` only when you know it is the one the placement was stored with. + It NARROWS the target — a placement recorded under a different id is not matched, + even when the sheet name and anchor agree, because the stored name may belong to a + tab that has since been renamed and the id is the only stable identity. If your id + may have moved on (a tab deleted and recreated under the same name reports a new + one), address the placement by `placementId`, or by sheet name with no + `sheetId` at all. + + A target that matches no placement succeeds and changes nothing, so retrying is + safe. + + Like connecting, this records nothing about the report's definition and so requires + only read access: unplacing a report from your own sheet is not an edit to the report. put: operationId: connectReportToWorkbook parameters: @@ -1650,6 +1774,13 @@ paths: required: true schema: type: number + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RefreshReportInput' + description: RefreshReportInput + required: false responses: '200': content: @@ -1793,6 +1924,19 @@ paths: required: true schema: type: integer + - in: query + name: after + schema: + oneOf: + - type: string + - type: 'null' + - in: query + name: first + schema: + oneOf: + - minimum: 1 + type: integer + - type: 'null' responses: '200': content: @@ -3163,6 +3307,20 @@ paths: /api/v1/github/installations: get: operationId: installations + parameters: + - in: query + name: after + schema: + oneOf: + - type: string + - type: 'null' + - in: query + name: first + schema: + oneOf: + - minimum: 1 + type: integer + - type: 'null' responses: '200': content: @@ -3181,7 +3339,20 @@ paths: name: installationId required: true schema: - type: number + type: integer + - in: query + name: after + schema: + oneOf: + - type: string + - type: 'null' + - in: query + name: first + schema: + oneOf: + - minimum: 1 + type: integer + - type: 'null' responses: '200': content: @@ -3196,11 +3367,11 @@ paths: get: operationId: branches parameters: - - in: query - name: installationId + - in: path + name: owner required: true schema: - type: integer + type: string - in: path name: repo required: true @@ -3208,9 +3379,22 @@ paths: type: string - in: query name: installationId - required: false + required: true schema: - type: number + type: integer + - in: query + name: after + schema: + oneOf: + - type: string + - type: 'null' + - in: query + name: first + schema: + oneOf: + - minimum: 1 + type: integer + - type: 'null' responses: '200': content: @@ -3425,9 +3609,23 @@ paths: /api/v1/regions: get: operationId: listRegions - responses: - '200': - content: + parameters: + - in: query + name: after + schema: + oneOf: + - type: string + - type: 'null' + - in: query + name: first + schema: + oneOf: + - minimum: 1 + type: integer + - type: 'null' + responses: + '200': + content: application/json: schema: $ref: '#/components/schemas/RegionsListResponse' @@ -3435,6 +3633,11 @@ paths: summary: List regions tags: - Regions + x-mint: + content: >- + Lists the regions available to the account, cursor-paginated via `first`/`after`. The + legacy `data` field returns the full, unpaginated list and is deprecated — use `items` + + `pageInfo` instead. /api/v1/spec: get: operationId: getSpec @@ -3555,6 +3758,15 @@ paths: oneOf: - $ref: '#/components/schemas/GetUserAttributesQueryType' - type: 'null' + - in: query + name: after + schema: + type: string + - in: query + name: first + schema: + minimum: 1 + type: integer responses: '200': content: @@ -3565,6 +3777,11 @@ paths: summary: Get user attributes tags: - User Attributes + x-mint: + content: >- + List user attributes, newest first, cursor-paginated via `first`/`after`. The legacy + `offset`/`limit` query params and the `data`/`count` response fields are deprecated — use + `items` + `pageInfo` instead. post: operationId: createUserAttribute requestBody: @@ -3829,6 +4046,53 @@ paths: `github`, no starter project is scaffolded — connect a repository afterwards via POST /build/api/v1/deployments/:deploymentId/github/connect. /build/api/v1/deployments/{deploymentId}/branches: + delete: + operationId: removeBranch + parameters: + - in: path + name: deploymentId + required: true + schema: + type: integer + - in: query + name: branchName + required: true + schema: + type: string + - in: query + name: removeOnUpstream + required: false + schema: + type: boolean + default: false + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DataModelWriteResponse' + description: '' + summary: Delete a branch + tags: + - Data Model + x-mint: + content: >- + Deletes the branch and its git ref — the API-side counterpart of pruning a branch in Cube + Cloud, for callers that create branches per run (a CI job syncing dbt on every pull + request, say) and would otherwise accumulate them forever. Child branches are re-parented + onto the deleted branch’s own parent, and anyone viewing the branch in dev mode is moved + back to the default branch, so a branch that is currently open is deleted rather than + refused. The deploy branch cannot be deleted (400) and an unknown branch is a 404. + + + `removeOnUpstream=true` additionally deletes the branch on the connected git provider + (GitHub/GitLab). It defaults to `false`, so by default the branch is removed from Cube + Cloud only and the ref on your own remote is left alone — a CI job cleaning up branches it + created itself should opt in explicitly. + + + `branchName` is a query parameter rather than a path segment because branch names contain + slashes (`dbt-sync/main-20260817120000-a1b2c3d4`). get: operationId: listBranches parameters: @@ -3837,6 +4101,19 @@ paths: required: true schema: type: integer + - in: query + name: after + schema: + oneOf: + - type: string + - type: 'null' + - in: query + name: first + schema: + oneOf: + - minimum: 1 + type: integer + - type: 'null' responses: '200': content: @@ -4019,6 +4296,19 @@ paths: required: false schema: type: boolean + - in: query + name: after + schema: + oneOf: + - type: string + - type: 'null' + - in: query + name: first + schema: + oneOf: + - minimum: 1 + type: integer + - type: 'null' responses: '200': content: @@ -4029,6 +4319,12 @@ paths: summary: List the data-model source files for a deployment tags: - Data Model + x-mint: + content: >- + Returns the data-model file tree. `first`/`after` (from the shared cursor params) paginate + the **top-level** tree nodes only — a single node can carry a large subtree, so this + bounds the number of roots returned, not the total file count. Omit `first` to get the + whole tree. put: operationId: updateFiles parameters: @@ -4207,6 +4503,43 @@ paths: summary: Start a data-model upload transaction tags: - Data Model Uploads + /build/api/v1/deployments/{deploymentId}/data-model/validate: + get: + operationId: validateDataModel + parameters: + - in: path + name: deploymentId + required: true + schema: + type: integer + - in: query + name: branchName + required: false + schema: + type: string + - in: query + name: devMode + required: false + schema: + type: boolean + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DataModelValidationResponse' + description: '' + summary: Compile a deployment's data model and report compilation errors + tags: + - Data Model + x-mint: + content: >- + Asks the branch's own Cube runtime for `GET /cubejs-api/v1/meta` — the same call the + console makes in dev mode — so the verdict is the one that branch's API would give. With + neither parameter it validates the deploy branch (production). `branchName` validates a + named branch, and `devMode=true` validates the caller's active dev-mode branch; the two + are mutually exclusive. Answers 200 with `valid: false` and the compiler's errors when the + model doesn't compile — a broken model is a result, not a request error. /build/api/v1/deployments/{deploymentId}/dev-mode: delete: operationId: stopDevMode @@ -4491,10 +4824,21 @@ components: BranchesListResponse: properties: data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' + items: + $ref: '#/components/schemas/BranchResponse' + type: array + items: items: $ref: '#/components/schemas/BranchResponse' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: + - items - data type: object BuildStatusResponse: @@ -4715,18 +5059,47 @@ components: type: object ConnectReportToWorkbookInput: properties: + anchorCell: + oneOf: + - type: string + - type: 'null' endResultCell: oneOf: - type: string - type: 'null' externalWorkbookId: type: string + host: + oneOf: + - $ref: '#/components/schemas/ConnectReportToWorkbookInputHost' + - type: 'null' + placementId: + oneOf: + - type: string + - type: 'null' resultLocation: type: string + sheetId: + oneOf: + - type: string + - type: 'null' + sheetName: + oneOf: + - type: string + - type: 'null' + workbookName: + oneOf: + - type: string + - type: 'null' required: - externalWorkbookId - resultLocation type: object + ConnectReportToWorkbookInputHost: + enum: + - GOOGLE_SHEETS + - EXCEL + type: string ControlThemeBorderSectionInput: properties: color: @@ -5076,6 +5449,10 @@ components: oneOf: - type: integer - type: 'null' + host: + oneOf: + - $ref: '#/components/schemas/CreateReportInputHost' + - type: 'null' jsonQuery: oneOf: - type: string @@ -5104,6 +5481,10 @@ components: oneOf: - type: string - type: 'null' + sheetId: + oneOf: + - type: string + - type: 'null' source: oneOf: - $ref: '#/components/schemas/CreateReportInputSource' @@ -5120,7 +5501,16 @@ components: oneOf: - type: integer - type: 'null' + workbookName: + oneOf: + - type: string + - type: 'null' type: object + CreateReportInputHost: + enum: + - GOOGLE_SHEETS + - EXCEL + type: string CreateReportInputSource: enum: - GOOGLE_SHEETS @@ -5928,6 +6318,7 @@ components: - FILTER - AI - TIME_GRAIN + - PARENT - SPACER - DIVIDER - CONTAINER @@ -5967,6 +6358,7 @@ components: - FILTER - AI - TIME_GRAIN + - PARENT - SPACER - DIVIDER - CONTAINER @@ -6030,6 +6422,47 @@ components: - path - newPath type: object + DataModelValidationError: + properties: + fileName: + oneOf: + - type: string + - type: 'null' + message: + type: string + required: + - message + type: object + DataModelValidationResponse: + properties: + branchName: + type: string + cubesCount: + oneOf: + - type: integer + - type: 'null' + deploymentId: + type: integer + errors: + items: + $ref: '#/components/schemas/DataModelValidationError' + type: array + mode: + $ref: '#/components/schemas/DataModelValidationResponseMode' + valid: + type: boolean + required: + - deploymentId + - branchName + - mode + - valid + - errors + type: object + DataModelValidationResponseMode: + enum: + - production + - dev_mode + type: string DataModelWriteResponse: properties: success: @@ -6052,6 +6485,49 @@ components: required: - path type: object + DbtSyncLogEntriesListResponse: + properties: + items: + items: + $ref: '#/components/schemas/DbtSyncLogEntry' + type: array + pageInfo: + $ref: '#/components/schemas/PageInfo' + required: + - items + - pageInfo + type: object + DbtSyncLogEntry: + properties: + durationMs: + oneOf: + - type: integer + description: For a phase line, how long the phase took, in milliseconds. + - type: 'null' + level: + description: info or error. + type: string + message: + type: string + phase: + oneOf: + - type: string + description: The phase this line belongs to, e.g. dbt-compile. + - type: 'null' + stream: + description: >- + Always "system" in this version. "stdout" and "stderr" join it once dbt’s own output is + captured, so treat this as an open set. + type: string + timestamp: + description: When the line was produced, as an ISO 8601 timestamp. + type: string + required: + - timestamp + - level + - stream + - message + type: object DbtSyncManifestStats: properties: macros: @@ -6125,6 +6601,189 @@ components: - completedAt - durationMs type: object + DbtSyncRun: + properties: + branchName: + description: The Cube branch this sync wrote its generated cubes to. + type: string + completedAt: + oneOf: + - type: string + description: >- + When the sync finished, as an ISO 8601 timestamp, or null while it is still running. + This is stamped by the process that ran the sync whereas startedAt is stamped by the + process that launched it — two different clocks — so for a run that failed moments + after starting, completedAt can even precede startedAt. Use durationMs to show how + long a run took rather than subtracting these. + - type: 'null' + deploymentId: + type: integer + durationMs: + oneOf: + - type: integer + description: >- + How long the sync took, in milliseconds, and the value to use when showing a + duration. Measured from a single clock, so it is never negative — prefer it over + subtracting startedAt from completedAt, which can disagree with it by a small skew. + - type: 'null' + errorMessage: + oneOf: + - type: string + description: Why the sync stopped. Present only for a failed run. + - type: 'null' + failedPhase: + oneOf: + - type: string + description: The phase that failed, e.g. dbt-compile. + - type: 'null' + gitRef: + oneOf: + - type: string + description: The dbt-repository ref this sync was run against, when pinned. + - type: 'null' + lastStage: + oneOf: + - type: string + description: The pipeline stage the run reached, e.g. COMPILING_DBT. + - type: 'null' + phases: + oneOf: + - items: + $ref: '#/components/schemas/DbtSyncRunPhase' + type: array + description: Per-phase timings, in the order they ran. Absent while the sync is running. + - type: 'null' + startedAt: + description: >- + When the sync started, as an ISO 8601 timestamp. See completedAt before comparing the + two. + type: string + stats: + oneOf: + - $ref: '#/components/schemas/DbtSyncRunStats' + - type: 'null' + status: + description: >- + RUNNING, COMPLETED, FAILED, CANCELLED or UNKNOWN. UNKNOWN means the outcome could not be + established (the run’s record was lost and the workflow engine no longer knows the + workflow) and is deliberately distinct from FAILED. Treat an unrecognized value as + non-terminal. + type: string + syncJobId: + type: string + trigger: + description: >- + What started the sync: manual (the Cube Cloud UI), api (this REST API), webhook (a push + to the dbt repository), agent (Cube AI), or unknown. + type: string + triggerContext: + oneOf: + - $ref: '#/components/schemas/DbtSyncRunTriggerContext' + - type: 'null' + userId: + oneOf: + - type: integer + description: The Cube user who started the sync, when a user started it. + - type: 'null' + required: + - syncJobId + - deploymentId + - status + - trigger + - branchName + - startedAt + type: object + DbtSyncRunListResponse: + properties: + items: + items: + $ref: '#/components/schemas/DbtSyncRun' + type: array + pageInfo: + $ref: '#/components/schemas/PageInfo' + required: + - items + - pageInfo + type: object + DbtSyncRunPhase: + properties: + durationMs: + description: How long the phase took, in milliseconds. + type: integer + outcome: + description: ok or failed. + type: string + phase: + description: Phase token, e.g. repo-clone, dbt-deps, dbt-compile. + type: string + startedOffsetMs: + description: When the phase began, in milliseconds after the sync started. + type: integer + required: + - phase + - startedOffsetMs + - durationMs + - outcome + type: object + DbtSyncRunStats: + properties: + cubeCount: + oneOf: + - type: integer + description: How many cubes the sync generated. + - type: 'null' + generatedFileCount: + oneOf: + - type: integer + description: How many files the sync wrote to the branch it created. + - type: 'null' + macros: + oneOf: + - type: integer + description: dbt macros found in the manifest. + - type: 'null' + models: + oneOf: + - type: integer + description: dbt models found in the manifest. + - type: 'null' + sources: + oneOf: + - type: integer + description: dbt sources found in the manifest. + - type: 'null' + type: object + DbtSyncRunTriggerContext: + properties: + headSha: + oneOf: + - type: string + - type: 'null' + pushedBranch: + oneOf: + - type: string + - type: 'null' + targetBranch: + oneOf: + - type: string + - type: 'null' + type: object + DbtSyncRunsQueryStatus: + enum: + - RUNNING + - COMPLETED + - FAILED + - CANCELLED + - UNKNOWN + type: string + DbtSyncRunsQueryTrigger: + enum: + - manual + - api + - webhook + - agent + - unknown + type: string DbtSyncStatusResponse: properties: error: @@ -6258,14 +6917,28 @@ components: DeploymentEnvironmentTokensListResponse: properties: data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' items: $ref: '#/components/schemas/DeploymentEnvironmentToken' type: array + items: + items: + $ref: '#/components/schemas/DeploymentEnvironmentToken' + type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' pagination: - $ref: '#/components/schemas/DeploymentsPagination' + oneOf: + - $ref: '#/components/schemas/DeploymentsPagination' + deprecated: true + description: 'Deprecated: use `pageInfo` instead. Kept for backward compatibility.' + - type: 'null' required: + - items - data - - pagination type: object DeploymentEnvironmentType: enum: @@ -6276,14 +6949,28 @@ components: DeploymentEnvironmentsListResponse: properties: data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' items: $ref: '#/components/schemas/DeploymentEnvironment' type: array + items: + items: + $ref: '#/components/schemas/DeploymentEnvironment' + type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' pagination: - $ref: '#/components/schemas/DeploymentsPagination' + oneOf: + - $ref: '#/components/schemas/DeploymentsPagination' + deprecated: true + description: 'Deprecated: use `pageInfo` instead. Kept for backward compatibility.' + - type: 'null' required: + - items - data - - pagination type: object DeploymentLogLine: properties: @@ -6317,6 +7004,10 @@ components: items: $ref: '#/components/schemas/DeploymentLogLine' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: - items type: object @@ -6355,6 +7046,10 @@ components: items: $ref: '#/components/schemas/DeploymentPod' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: - items type: object @@ -6371,9 +7066,7 @@ components: - $ref: '#/components/schemas/DeploymentSettingsCreationMethod' - type: 'null' creationStep: - oneOf: - - $ref: '#/components/schemas/CreationStep' - - type: 'null' + $ref: '#/components/schemas/CreationStep' cspsConfig: oneOf: - $ref: '#/components/schemas/CspsConfig' @@ -6407,9 +7100,7 @@ components: deployProjectRoot: type: string deploymentUrl: - oneOf: - - type: string - - type: 'null' + type: string id: type: integer isCold: @@ -6466,6 +7157,8 @@ components: - name - template - cloudProvider + - creationStep + - deploymentUrl - releaseChannel - deployBranchName - deployBranchReadOnly @@ -6541,12 +7234,16 @@ components: type: string DeploymentVersionsResponse: properties: - data: + items: items: $ref: '#/components/schemas/DeploymentVersion' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: - - data + - items type: object DeploymentsListResponse: properties: @@ -6554,8 +7251,14 @@ components: oneOf: - minimum: 0 type: integer + deprecated: true + description: >- + Deprecated: total number of accessible deployments, ignoring pagination. Kept for + backward compatibility. - type: 'null' data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' items: $ref: '#/components/schemas/Deployment' type: array @@ -6569,14 +7272,18 @@ components: - type: 'null' pagination: $ref: '#/components/schemas/DeploymentsPagination' + deprecated: true + description: 'Deprecated: use `pageInfo` instead. Kept for backward compatibility.' totalCount: oneOf: - minimum: 0 type: integer + deprecated: true + description: 'Deprecated: use `count`. Kept for backward compatibility.' - type: 'null' required: - - data - items + - data - pagination type: object DeploymentsPagination: @@ -6610,6 +7317,33 @@ components: required: - active type: object + DisconnectReportFromWorkbookInput: + properties: + anchorCell: + oneOf: + - type: string + - type: 'null' + externalWorkbookId: + oneOf: + - type: string + - type: 'null' + placementId: + oneOf: + - type: string + - type: 'null' + resultLocation: + oneOf: + - type: string + - type: 'null' + sheetId: + oneOf: + - type: string + - type: 'null' + sheetName: + oneOf: + - type: string + - type: 'null' + type: object DuplicateWorkbookInput: properties: shared: @@ -6727,6 +7461,20 @@ components: type: object EmbedSessionSettings: properties: + allowChatWorkspaceAuthoring: + oneOf: + - type: boolean + description: >- + Whether AI Chat authenticated with this session may create or modify persistent Cube + Workspace content. Set to `false` for headless chat integrations that render answers + in their own UI: ad-hoc data analysis and inline tables/charts remain available, but + the agent cannot save or update standalone explorations/reports, create or modify + workbooks, create or publish dashboards, or direct users to those Cube UI surfaces. + Omit or set to `true` to preserve the session user's role-derived authoring + capabilities; `true` never grants access the user does not already have. This + setting changes only AI Chat tools and instructions. It does not change user roles + or permissions for direct API calls. + - type: 'null' showDashboardChat: oneOf: - type: boolean @@ -6923,10 +7671,21 @@ components: EnvVariablesListResponse: properties: data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' + items: + $ref: '#/components/schemas/EnvVariableItem' + type: array + items: items: $ref: '#/components/schemas/EnvVariableItem' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: + - items - data type: object FileHash: @@ -7157,9 +7916,9 @@ components: - $ref: '#/components/schemas/EmbedSessionSettings' type: object description: >- - Per-session overrides for embed behavior. Each key is tri-state: omit it to inherit - the account-wide setting, or set `true`/`false` to force the behavior for every - embed viewed with this session, taking precedence over the account-wide setting. + Per-session overrides for embed behavior, stored in the signed embed token. Each + documented key applies only to this session; omitted keys preserve their existing + default behavior. - type: 'null' tenantGroupDefinitions: oneOf: @@ -7268,10 +8027,21 @@ components: GitHubBranchesListResponse: properties: data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' + items: + $ref: '#/components/schemas/GitHubBranchResponse' + type: array + items: items: $ref: '#/components/schemas/GitHubBranchResponse' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: + - items - data type: object GitHubConnectStatusResponse: @@ -7306,10 +8076,21 @@ components: GitHubInstallationsListResponse: properties: data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' + items: + $ref: '#/components/schemas/GitHubInstallationResponse' + type: array + items: items: $ref: '#/components/schemas/GitHubInstallationResponse' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: + - items - data type: object GitHubRepoResponse: @@ -7328,10 +8109,21 @@ components: GitHubReposListResponse: properties: data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' + items: + $ref: '#/components/schemas/GitHubRepoResponse' + type: array + items: items: $ref: '#/components/schemas/GitHubRepoResponse' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: + - items - data type: object GroupDefinition: @@ -7793,6 +8585,14 @@ components: items: type: string type: array + measuresAxis: + oneOf: + - $ref: '#/components/schemas/PivotItemsMeasuresAxis' + - type: 'null' + measuresPosition: + oneOf: + - $ref: '#/components/schemas/PivotItemsMeasuresPosition' + - type: 'null' rows: items: type: string @@ -7817,6 +8617,14 @@ components: items: type: string type: array + measuresAxis: + oneOf: + - $ref: '#/components/schemas/PivotItemsInputMeasuresAxis' + - type: 'null' + measuresPosition: + oneOf: + - $ref: '#/components/schemas/PivotItemsInputMeasuresPosition' + - type: 'null' rows: items: type: string @@ -7827,6 +8635,26 @@ components: - rows - filters type: object + PivotItemsInputMeasuresAxis: + enum: + - columns + - rows + type: string + PivotItemsInputMeasuresPosition: + enum: + - before + - after + type: string + PivotItemsMeasuresAxis: + enum: + - columns + - rows + type: string + PivotItemsMeasuresPosition: + enum: + - before + - after + type: string PostTokenBySessionIdInput: properties: sessionId: @@ -7899,6 +8727,33 @@ components: - stderr - inputRowCount type: object + RefreshReportInput: + properties: + anchorCell: + oneOf: + - type: string + - type: 'null' + externalWorkbookId: + oneOf: + - type: string + - type: 'null' + placementId: + oneOf: + - type: string + - type: 'null' + resultLocation: + oneOf: + - type: string + - type: 'null' + sheetId: + oneOf: + - type: string + - type: 'null' + sheetName: + oneOf: + - type: string + - type: 'null' + type: object RegionResponse: properties: id: @@ -7936,10 +8791,21 @@ components: RegionsListResponse: properties: data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' + items: + $ref: '#/components/schemas/RegionResponse' + type: array + items: items: $ref: '#/components/schemas/RegionResponse' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: + - items - data type: object RemoveDataModelFilesInput: @@ -8151,18 +9017,63 @@ components: type: string ReportPlacement: properties: + anchorCell: + oneOf: + - type: string + - type: 'null' endResultCell: oneOf: - type: string - type: 'null' + host: + oneOf: + - $ref: '#/components/schemas/ReportPlacementHost' + - type: 'null' + id: + oneOf: + - type: string + - type: 'null' + placedAt: + oneOf: + - type: string + - type: 'null' + placedByUserId: + oneOf: + - type: integer + - type: 'null' + refreshedAt: + oneOf: + - type: string + - type: 'null' + refreshedByUserId: + oneOf: + - type: integer + - type: 'null' resultLocation: type: string + sheetId: + oneOf: + - type: string + - type: 'null' + sheetName: + oneOf: + - type: string + - type: 'null' workbookId: type: string + workbookName: + oneOf: + - type: string + - type: 'null' required: - workbookId - resultLocation type: object + ReportPlacementHost: + enum: + - GOOGLE_SHEETS + - EXCEL + type: string ReportSnapshot: properties: description: @@ -8202,6 +9113,10 @@ components: oneOf: - $ref: '#/components/schemas/ReportSnapshotPreferences' - type: 'null' + pythonCode: + oneOf: + - type: string + - type: 'null' reportId: type: integer spec: @@ -8398,11 +9313,23 @@ components: SourceTreeResponse: properties: data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' + items: + additionalProperties: true + type: object + type: array + items: items: additionalProperties: true type: object type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: + - items - data type: object StartDbtSyncInput: @@ -8870,6 +9797,10 @@ components: oneOf: - type: integer - type: 'null' + host: + oneOf: + - $ref: '#/components/schemas/UpdateReportInputHost' + - type: 'null' jsonQuery: oneOf: - type: string @@ -8887,6 +9818,10 @@ components: oneOf: - $ref: '#/components/schemas/PivotItems' - type: 'null' + placementId: + oneOf: + - type: string + - type: 'null' publicId: oneOf: - pattern: ^[0-9A-Za-z]+$ @@ -8898,6 +9833,10 @@ components: oneOf: - type: string - type: 'null' + sheetId: + oneOf: + - type: string + - type: 'null' sqlQuery: oneOf: - type: string @@ -8910,7 +9849,16 @@ components: oneOf: - type: integer - type: 'null' + workbookName: + oneOf: + - type: string + - type: 'null' type: object + UpdateReportInputHost: + enum: + - GOOGLE_SHEETS + - EXCEL + type: string UpdateWorkbookInput: properties: folderId: @@ -9271,12 +10219,28 @@ components: UserAttributesListResponse: properties: count: + deprecated: true + description: >- + Deprecated: total number of user attributes, ignoring pagination. Kept for backward + compatibility. + minimum: 0 type: integer data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' + items: + $ref: '#/components/schemas/UserAttribute' + type: array + items: items: $ref: '#/components/schemas/UserAttribute' type: array + pageInfo: + oneOf: + - $ref: '#/components/schemas/PageInfo' + - type: 'null' required: + - items - data - count type: object @@ -9690,8 +10654,14 @@ components: oneOf: - minimum: 0 type: integer + deprecated: true + description: >- + Deprecated: total number of accessible workbooks, ignoring pagination. Kept for + backward compatibility. - type: 'null' data: + deprecated: true + description: 'Deprecated: use `items` instead. Kept for backward compatibility.' items: $ref: '#/components/schemas/Workbook' type: array diff --git a/docs-mintlify/api-reference/changelog.mdx b/docs-mintlify/api-reference/changelog.mdx index d9815203b6e03..d99963b7378a0 100644 --- a/docs-mintlify/api-reference/changelog.mdx +++ b/docs-mintlify/api-reference/changelog.mdx @@ -7,6 +7,63 @@ rss: true {/* GENERATED FILE — do not edit by hand. */} {/* Run scripts/extract-changelog.js against the platform client CHANGELOG.md. */} + + ### Added + + #### dbt Sync — status, results and history + + - `GET /api/v1/deployments/{deploymentId}/dbt-sync` (`DbtSyncPublicController.listDbtSyncs`) — sync run history, filterable by `status`/`trigger`, cursor-paginated. New schemas: `DbtSyncRun`, `DbtSyncRunListResponse`, `DbtSyncRunPhase`, `DbtSyncRunStats`, `DbtSyncRunTriggerContext`, `DbtSyncRunsQueryStatus`, `DbtSyncRunsQueryTrigger`. + - `GET /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}` (`getDbtSyncStatus`) — poll a running sync. New schemas: `DbtSyncStatusResponse`, `DbtSyncProgress`. + - `GET .../dbt-sync/{syncJobId}/result` (`getDbtSyncResult`) and `GET .../dbt-sync/{syncJobId}/logs` (`getDbtSyncLogs`). New schemas: `DbtSyncResultResponse`, `DbtSyncGeneratedFile`, `DbtSyncManifestStats`, `DbtSyncLogEntry`, `DbtSyncLogEntriesListResponse`. + - `DELETE .../dbt-sync/{syncJobId}` (`cancelDbtSync`) — cancel a running sync. New schema: `DbtSyncCancelResponse`. + + #### Data model — branch cleanup, staging environments and validation + + - `DELETE /build/api/v1/deployments/{deploymentId}/branches` (`DataModelPublicController.removeBranch`, `branchName` as a query param since branch names contain slashes) — deletes a branch and its git ref, re-parenting any children onto the deleted branch's own parent; `removeOnUpstream` additionally deletes the ref on the connected GitHub/GitLab remote. New schema: `DataModelWriteResponse` (shared with the existing file-write endpoints). + - `PUT .../branches/staging-environment` (`setBranchStagingEnvironment`) — enable or disable a branch's always-on staging environment (`/dev-mode/{branchName}/cubejs-api/v1`). New schemas: `SetBranchStagingEnvironmentRequest`, `SetBranchStagingEnvironmentResponse`. `BranchResponse` (from `listBranches`) reads the flag back via a new `isStagingEnvironmentEnabled`. + - `GET .../data-model/validate` (`validateDataModel`) — compiles the deploy branch, a named branch, or the caller's dev-mode branch and reports compilation errors as a 200 response (`valid: false`), not a request error. New schemas: `DataModelValidationResponse`, `DataModelValidationError`, `DataModelValidationResponseMode`. + + #### Deployment settings, versions and the spec endpoint + + - `GET /api/v1/deployments/{deploymentId}/settings` (`DeploymentsPublicController.getDeploymentSettings`) — every deployment setting in one payload (secrets excluded); writes go through the existing `PUT /:deploymentId`. New schema: `DeploymentSettings` (+ its `cloudProvider`/`creationMethod`/`deployMode`/`releaseChannel`/`repoType`/`template` enums). + - `GET /api/v1/deployments/{deploymentId}/versions` (`listDeploymentVersions`) — the Cube versions this deployment can switch to (release-channel heads plus prior versions run before). New schemas: `DeploymentVersion`, `DeploymentVersionsResponse`, `DeploymentVersionReleaseChannel`. + - `PUT /api/v1/deployments/{deploymentId}` (`updateDeployment`) / `UpdateDeploymentInput` now accepts `cloudProvider`, `region`, `releaseChannel`, `releaseChannelVersion`, `releaseChannelVersionHold`, `template`, `templateVariables`, `defaultLaunchpadViewGroup`, and `launchpadTabs` — the same settings surface `GET .../settings` now reads back. `releaseChannelVersion` must be one of the versions `GET .../versions` lists for the target channel. + - `GET /api/v1/spec` (`OpenApiSpecPublicController.getSpec`) — the full OpenAPI 3.1 document served by the build handling the request, for runtime discovery by clients and agents. + + #### Reports + + - `DELETE /api/v1/deployments/{deploymentId}/reports/{reportId}/connect-workbook` (`ReportsPublicController.disconnectReportFromWorkbook`) — removes a single sheet+anchor placement of a report from a spreadsheet, leaving every other placement (in this workbook and others) alone. Addressed by `placementId`, or by `externalWorkbookId` + `sheetName` + `anchorCell`. New schema: `DisconnectReportFromWorkbookInput`. + - `PUT .../reports/{reportId}/refresh` now takes a body: new schema `RefreshReportInput` (`placementId`, or `externalWorkbookId` + `sheetName`/`sheetId`/`anchorCell`), addressing the same way as disconnect. + - Report placements are now individually addressable and trackable (new `ConnectReportToWorkbookInputHost` / `CreateReportInputHost` / `UpdateReportInputHost` enums, `"GOOGLE_SHEETS" | "EXCEL"`): `ConnectReportToWorkbookInput` gained `placementId`, `sheetId`, `sheetName`, `anchorCell`, `workbookName` and `host`; `CreateReportInput` gained `host`, `sheetId` and `workbookName`; `UpdateReportInput` gained `host`, `placementId`, `sheetId` and `workbookName`. `ReportPlacement` gained the matching `id`, `host`, `sheetId`, `sheetName`, `anchorCell`, `workbookName`, plus `placedAt`/`placedByUserId` and `refreshedAt`/`refreshedByUserId` (new `ReportPlacementHost` enum). `ReportSnapshot` gained `isSourceReport` and `pythonCode`. + + #### Cursor pagination on previously offset-only or unpaginated lists + + - `first`/`after` → `items` + `pageInfo` cursor pagination added to: deployment env variables (`GET .../env-vars`), GitHub branches/installations/repositories (`GitHubPublicController.*`), user attributes (previously `offset`/`limit`), `GET /api/v1/regions/`, report folders, data-model branches and files (`listBranches`/`listFiles`, `BranchesListResponse`/`SourceTreeResponse`), and deployment logs/pods (`DeploymentLogsResponse`/`DeploymentPodsResponse` gain `pageInfo`). Newly-paginated responses keep the legacy `data`/`count`/`pagination` fields, marked `@deprecated`. + - `UserGroupsPublicController.listGroups` and `UsersPublicController.getUsers` already had `first`/`after`; what's new on both is a `search` query param (case-insensitive substring match on group name / first name, username, email). + - `DashboardFilterOperator` / `DashboardFilterInputOperator` gained `is_empty` / `is_not_empty`. + + #### Other new fields + + - New `TimezoneSettings` schema, plus `timezoneSettings` on `AppConfigResponse` and `resolvedTimezone` / `deployBranchName` / `timezoneSettings` on `AIEngineerSettings`. + - New `CspsConfig` schema (customer-storage-provider config, mirrored on the existing `CspsConfigInput`). + - `scopeDeploymentId` added to the agent-space config schemas. + - `measuresAxis` / `measuresPosition` added to `PivotItems` / `PivotItemsInput` (new `PivotItemsMeasuresAxis` / `PivotItemsMeasuresPosition` / `PivotItemsInputMeasuresAxis` / `PivotItemsInputMeasuresPosition` enums, `"columns" | "rows"` and `"before" | "after"`). + - `name` / `userCount` added to `InheritedGroupPolicyDto` and `ResourceGroupPolicyDto`. + - `allowChatWorkspaceAuthoring` added to `EmbedSessionSettings`. + - `ref` added to `StartDbtSyncInput`. + + ### Changed + + - **BREAKING (TypeScript):** `DeploymentEnvironmentsListResponse.pagination` and `DeploymentEnvironmentTokensListResponse.pagination` moved from required to `pagination?: DeploymentsPagination | null` now that both responses also carry `items`/`pageInfo`. Code under `strictNullChecks` reading `res.pagination.currentPage` without a null check no longer compiles. + - **BREAKING (TypeScript):** `NotificationsPublicController.getRecipients`'s cursor params were narrowed — `first?: number | string | null` → `first?: number | null` and `after?: string | number | null` → `after?: string | null` — cleaning up a hand-declared `@OpenAPI({ parameters })` that had merged positionally with the real cursor-param types. A caller passing a numeric `after` or a stringified `first` (both accepted, if oddly typed, before) no longer compiles. + - `DashboardWidgetDtoType` / `DashboardWidgetInputType` no longer advertise `TABS_CONTAINER` in the type — replaced by `PARENT`, `SPACER`, `DIVIDER`, `CONTAINER` (a `CONTAINER` with `config.layout: 'tabs'` is the current shape for a tabbed container). Existing stored or sent `TABS_CONTAINER` values are still healed server-side for backward compatibility (CUB-3634), so this narrows the advertised type rather than breaking existing integrations at runtime — new code should target `CONTAINER` + `layout`. + - `PUT /api/v1/deployments/{deploymentId}` (`updateDeployment`) description clarified: writes are a partial merge (omitted fields are left alone, `templateVariables` is merged key-by-key), and `releaseChannelVersion` is validated against `GET .../versions`. + + ### Removed + + - **BREAKING:** `POST /api/v1/deployments/{deploymentId}/workbooks/{workbookId}/dashboard/ai-widget-thread` (`WorkbooksPublicController.updatePublishedDashboardAiWidgetThread`) and its `UpdatePublishedAiWidgetThreadInput` schema (added in `0.2.0`). It persisted an AI-analysis thread id + checksum into the published dashboard config — the mechanism that caused read-only/anonymous viewers to hit `WorkbookEdit`/403s and let one viewer's filter state clobber the shared config (CUB-3898). AI-analysis results are now cached server-side keyed by dashboard state, so no client-facing endpoint replaces it. This is a deliberate breaking removal; SDK consumers referencing the operation or schema should drop those references. + + > Supersedes `0.2.1`, which never reached npm because its publish job failed. > Upgrading from `0.2.0` picks up both releases, so the `0.2.1` entries are diff --git a/docs-mintlify/api-reference/introduction.mdx b/docs-mintlify/api-reference/introduction.mdx index d7f69f6cb67cb..33c7d132f9412 100644 --- a/docs-mintlify/api-reference/introduction.mdx +++ b/docs-mintlify/api-reference/introduction.mdx @@ -79,7 +79,7 @@ Resources by entity: | [Data Model Uploads](/api-reference/data-model-uploads/content-hashes-of-the-current-data-model-files-for-upload-diffing) | `/build/api/v1/deployments/{deploymentId}/data-model` | v1 | | [GitHub](/api-reference/github/list-repositories-accessible-to-a-github-app-installation) | `/api/v1/github` | v1 | | [GitHub Connection](/api-reference/github-connection/connect-a-deployment-to-a-github-repository-and-trigger-a-build) | `/build/api/v1/deployments/{deploymentId}/github/connect` | v1 | -| [dbt Sync](/api-reference/dbt-sync/start-a-dbt-sync-for-a-deployment) | `/api/v1/deployments/{deploymentId}/dbt-sync` | v1 | +| [dbt Sync](/api-reference/dbt-sync/list-dbt-syncs-for-a-deployment) | `/api/v1/deployments/{deploymentId}/dbt-sync` | v1 | | [Folders](/api-reference/folders/list-folders) | `/api/v1/deployments/{deploymentId}/folders` | v1 | | [Reports](/api-reference/reports/list-reports) | `/api/v1/deployments/{deploymentId}/reports` | v1 | | [Workbooks](/api-reference/workbooks/get-workbooks) | `/api/v1/deployments/{deploymentId}/workbooks` | v1 | diff --git a/docs-mintlify/docs.json b/docs-mintlify/docs.json index 90bed16172475..b86750bd9413f 100644 --- a/docs-mintlify/docs.json +++ b/docs-mintlify/docs.json @@ -71,6 +71,7 @@ "docs/explore-analyze/charts/chart-types/area", "docs/explore-analyze/charts/chart-types/scatter", "docs/explore-analyze/charts/chart-types/pie", + "docs/explore-analyze/charts/chart-types/sankey", "docs/explore-analyze/charts/chart-types/heatmap", "docs/explore-analyze/charts/chart-types/boxplot", "docs/explore-analyze/charts/chart-types/table", @@ -674,6 +675,7 @@ "recipes/data-modeling/dbt", "recipes/data-modeling/using-dynamic-measures", "recipes/data-modeling/dynamic-union-tables", + "recipes/data-modeling/cross-data-source-queries", "recipes/data-modeling/polymorphic-cubes", "recipes/data-modeling/custom-order" ] @@ -808,12 +810,14 @@ "pages": [ "GET /build/api/v1/deployments/{deploymentId}/branches", "POST /build/api/v1/deployments/{deploymentId}/branches", + "DELETE /build/api/v1/deployments/{deploymentId}/branches", "PUT /build/api/v1/deployments/{deploymentId}/branches/staging-environment", "POST /build/api/v1/deployments/{deploymentId}/commit", "GET /build/api/v1/deployments/{deploymentId}/data-model/files", "PUT /build/api/v1/deployments/{deploymentId}/data-model/files", "DELETE /build/api/v1/deployments/{deploymentId}/data-model/files", "POST /build/api/v1/deployments/{deploymentId}/data-model/files/rename", + "GET /build/api/v1/deployments/{deploymentId}/data-model/validate", "POST /build/api/v1/deployments/{deploymentId}/dev-mode", "DELETE /build/api/v1/deployments/{deploymentId}/dev-mode", "POST /build/api/v1/deployments/{deploymentId}/merge", @@ -852,9 +856,11 @@ "group": "dbt Sync", "openapi": "/api-reference/api.yaml", "pages": [ + "GET /api/v1/deployments/{deploymentId}/dbt-sync", "POST /api/v1/deployments/{deploymentId}/dbt-sync", "GET /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}", "DELETE /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}", + "GET /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}/logs", "GET /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}/result" ] }, @@ -880,6 +886,7 @@ "PUT /api/v1/deployments/{deploymentId}/reports/{reportId}", "DELETE /api/v1/deployments/{deploymentId}/reports/{reportId}", "PUT /api/v1/deployments/{deploymentId}/reports/{reportId}/connect-workbook", + "DELETE /api/v1/deployments/{deploymentId}/reports/{reportId}/connect-workbook", "PUT /api/v1/deployments/{deploymentId}/reports/{reportId}/refresh" ] }, diff --git a/docs-mintlify/docs/data-modeling/concepts/data-blending.mdx b/docs-mintlify/docs/data-modeling/concepts/data-blending.mdx index 044535d218ea9..f7bf3e6c5c35b 100644 --- a/docs-mintlify/docs/data-modeling/concepts/data-blending.mdx +++ b/docs-mintlify/docs/data-modeling/concepts/data-blending.mdx @@ -18,6 +18,11 @@ efficient for large volumes of data. The other situation in which data blending could be a better approach than joining on date is when the two tables have mostly the same columns, such as in the example below. +This pattern builds one cube over a union of the underlying cubes' SQL, so all of +them must live in the same data source. To append rows from cubes in *different* +databases, see [querying across data +sources](/recipes/data-modeling/cross-data-source-queries). + For an example, consider an omnichannel store which has both online and offline sales. Let's calculate summary metrics for revenue, customer count, etc. We have a `retail_orders` cube for offline sales: diff --git a/docs-mintlify/docs/explore-analyze/analytics-chat.mdx b/docs-mintlify/docs/explore-analyze/analytics-chat.mdx index a5c4671f82d56..d92407675d616 100644 --- a/docs-mintlify/docs/explore-analyze/analytics-chat.mdx +++ b/docs-mintlify/docs/explore-analyze/analytics-chat.mdx @@ -13,10 +13,10 @@ The AI agent interprets your questions, generates queries against your semantic - **Natural language queries** – Ask questions in plain language without knowing SQL - **Multi-query reasoning** – The AI creates and synthesizes multiple queries for complex questions -- **Python analysis** – For work SQL can't express — forecasting, cohort analysis, statistical tests — the agent can run [Python](/docs/explore-analyze/workbooks/python-analysis) and render the result inline, then save it to a workbook as a re-runnable report (in preview) +- **Python analysis** – For work SQL can't express — forecasting, cohort analysis, statistical tests — the agent can run [Python](/docs/explore-analyze/workbooks/python-analysis) and render the result inline, then save it as a re-runnable report or exploration (in preview) - **Semantic model integration** – All queries run against your semantic model with proper access control and security, honoring the active [security context](/docs/explore-analyze/workbooks/querying-data#applying-a-security-context)—including an override applied by a developer or admin - **Queued messages** – Send follow-up messages while the agent is still processing -- **Save to Workbook** – Export insights to [Workbooks](/docs/explore-analyze/workbooks) for further analysis +- **Save your results** – Ask the agent to save a result as a [report](/docs/explore-analyze/workbooks) inside a workbook, or as a standalone [exploration](/docs/explore-analyze/explore#saving-explorations) when you don't want to create a workbook ## Discover available fields diff --git a/docs-mintlify/docs/explore-analyze/charts/chart-types/index.mdx b/docs-mintlify/docs/explore-analyze/charts/chart-types/index.mdx index 1783f2f1b7c42..caeb9d3fdbd4d 100644 --- a/docs-mintlify/docs/explore-analyze/charts/chart-types/index.mdx +++ b/docs-mintlify/docs/explore-analyze/charts/chart-types/index.mdx @@ -10,6 +10,7 @@ Cube includes a library of built-in chart types covering the most common visuali - [Area](/docs/explore-analyze/charts/chart-types/area) - [Scatter](/docs/explore-analyze/charts/chart-types/scatter) - [Pie & donut](/docs/explore-analyze/charts/chart-types/pie) +- [Sankey](/docs/explore-analyze/charts/chart-types/sankey) - [Heatmap](/docs/explore-analyze/charts/chart-types/heatmap) - [Boxplot](/docs/explore-analyze/charts/chart-types/boxplot) - [Table](/docs/explore-analyze/charts/chart-types/table) @@ -79,9 +80,9 @@ Otherwise Cube checks these in order and takes the first that matches: The two [bar](/docs/explore-analyze/charts/chart-types/bar) rows are variants of the same chart type, not separate ones. -Pie, area, boxplot and HTML are never recommended — pick them yourself. Pie's shape, one measure +Pie, sankey, area, boxplot and HTML are never recommended — pick them yourself. Pie's shape, one measure across a few categories, is the one Bar already answers, and a bar chart compares those values more -accurately. All four stay available in the picker like any other chart type. +accurately. All five stay available in the picker like any other chart type. ### When nothing is recommended diff --git a/docs-mintlify/docs/explore-analyze/charts/chart-types/sankey.mdx b/docs-mintlify/docs/explore-analyze/charts/chart-types/sankey.mdx new file mode 100644 index 0000000000000..86b439cdf11fb --- /dev/null +++ b/docs-mintlify/docs/explore-analyze/charts/chart-types/sankey.mdx @@ -0,0 +1,75 @@ +--- +title: Sankey +description: Show flows between nodes, such as order status to year or channel to conversion. +--- + +Sankey diagrams draw one node per distinct endpoint and one ribbon per flow, with each ribbon's thickness proportional to its value. Best for showing how a quantity divides and moves between two sets of things: order statuses across years, traffic channels into outcomes, or one step of a user journey into the next. + +{/* Screenshot: sankey chart — order counts flowing from four statuses on the left to years on the right, with node labels on both sides. Place directly below this paragraph, full width. (hidden — replace this comment with when image is ready) */} + +## Fields + +A sankey needs exactly three fields, picked on the Fields tab: + +- **Source** — a dimension (a time dimension works too, labelled by its grain). The node a flow leaves. +- **Target** — a dimension. The node a flow enters. +- **Value** — a measure. Each row's value becomes one ribbon's thickness. + +Each result row is read as one flow, so a query grouped by two dimensions with one measure draws directly. **Swap** exchanges the source and target, which reverses every ribbon's direction. + +{/* Screenshot: Fields tab with the Source, Target and Value selectors bound, and the Swap button below them. Place inline, 50% width, right-aligned. (hidden — replace this comment with when image is ready) */} + +Stages come from shared node names rather than from a stage field: rows `A → B` and `B → C` draw as a three-stage diagram, because `B` appears as both a target and a source. Two dimensions that share no values always draw two stages. + +A row with a blank endpoint, or a value of zero or less, is not a flow and is left out — a sankey cannot draw a ribbon with no thickness. + +Cyclic data is rejected rather than drawn. A flow that returns to a node it already passed through has no left-to-right layout, so the chart names the loop and asks you to remove one hop. + +## Node grouping + +A wide breakdown can return more flows than a diagram can carry, and the small ones become unreadable first. Node grouping folds them into a single **Other** node per side, so the tail stays represented as an aggregate instead of vanishing. It is off until you pick a mode, in the Node grouping section of the Fields tab: + +- **Minimal share** — fold every node below a percentage of its side's total. A node sitting exactly on the threshold is kept. +- **Top N nodes** — keep the N largest nodes per side. Tied nodes at the cutoff are kept together, so asking for the top 3 can keep more than 3 when third place is shared. + +The two sides fold independently, each against its own total, because a node that is small as a target may be large as a source. The bucket's name is yours to set; leave it blank for **Other**. When both sides fold at once, the two buckets are named apart — **Other (source)** and **Other (target)** — so one is not read as the other. + +{/* Screenshot: Node grouping section with Minimal share selected and a threshold entered. Place inline, 50% width, right-aligned. (hidden — replace this comment with when image is ready) */} + +A single node is never folded on its own — replacing one named node with a same-sized **Other** would say less than leaving it alone. A node whose own value already matches the bucket's name is also left out of the bucket, so a real category called "Other" is never merged into it. + +The bucket stands for a set of members rather than one, so it has no query behind it: clicking a ribbon that touches it does not drill down. + +## Truncation + +Independently of grouping, a sankey draws at most 50 flows, keeping the largest. This is a backstop against a runaway result, not a legibility control — when it removes anything, a warning appears beside the Flows heading on the Fields tab saying how many of the total it is showing, pointing at node grouping, which is the real answer for a diagram too wide to read. + +## Data labels + +Each node carries its name and, optionally, its value. Both are independent buttons in the Data labels section of the Style tab: **Name** is on by default, **Value** is off. With both on, the value follows the name in parentheses; with only the value on, the node carries a bare number; with both off the diagram draws no labels. + +A node's value is the larger of its inflow and its outflow, and takes the value field's own number format. + +Labels sit to the right of their node, except in the final stage, where they flip to the left so they stay inside the frame. + +## Color + +Each node takes its own color from the palette selected in the **Palette** dropdown on the Style tab. The color follows the node's name rather than its position, so filtering or re-sorting the query does not repaint the diagram. See [Palettes](/docs/explore-analyze/charts/configuration/color-and-stacking#palettes) for the built-in palettes and how to define a custom one. + +The **Flows** control sets where a ribbon takes its color from: + +- **Source** — the color of the node the ribbon leaves. The default. +- **Neutral** — one muted color for every ribbon, leaving the nodes to carry the palette. +- **Target** — the color of the node the ribbon enters. + +{/* Screenshot: Style tab Color section with the Palette dropdown and the Flows buttons. Place inline, 50% width, right-aligned. (hidden — replace this comment with when image is ready) */} + +## Tooltips + +Hovering a ribbon shows the result columns named in **Tooltip fields** on the Fields tab, which starts as every column in the query except the three the diagram already draws — source, target and value. Add them back like any other field if you want them repeated. Hovering a node shows its name and value. + +See [Tooltips](/docs/explore-analyze/charts/configuration/tooltips) for adding, removing and reordering fields. + +## Drill down + +Clicking a ribbon drills into the row behind that flow. Ribbons touching a grouped **Other** node are not clickable, since that node covers several members at once. diff --git a/docs-mintlify/docs/explore-analyze/charts/configuration/series-mapping.mdx b/docs-mintlify/docs/explore-analyze/charts/configuration/series-mapping.mdx index 4489b16d23a1a..33a5c2caea8dd 100644 --- a/docs-mintlify/docs/explore-analyze/charts/configuration/series-mapping.mdx +++ b/docs-mintlify/docs/explore-analyze/charts/configuration/series-mapping.mdx @@ -18,6 +18,8 @@ Series mapping controls which query columns are assigned to which chart channels | **Split by** | Repeats the chart once per unique value, as a grid of panels — see [small multiples](/docs/explore-analyze/charts/configuration/small-multiples) | Bar, line, area, scatter | | **Tooltip** | Fields shown on hover | All types | | **Theta** (pie) | The measure that determines slice size | Pie | +| **Source** / **Target** (sankey) | The dimensions holding the node each flow leaves and enters | Sankey | +| **Value** (sankey) | The measure that determines ribbon thickness | Sankey | ## Assigning fields diff --git a/docs-mintlify/docs/explore-analyze/charts/configuration/small-multiples.mdx b/docs-mintlify/docs/explore-analyze/charts/configuration/small-multiples.mdx index 71b2a4ee07ada..14a1506a50607 100644 --- a/docs-mintlify/docs/explore-analyze/charts/configuration/small-multiples.mdx +++ b/docs-mintlify/docs/explore-analyze/charts/configuration/small-multiples.mdx @@ -55,6 +55,6 @@ A legend is shared across the grid rather than repeated per panel. Clicking a le ## Limitations - **One split dimension.** One dimension fills the grid, panel by panel. Splitting by two dimensions at once — one down the rows and another across the columns — is not supported. -- **Cartesian charts only.** Bar, line, area, and scatter. Pie, table, KPI, heatmap, boxplot, map, and HTML charts cannot be split. +- **Cartesian charts only.** Bar, line, area, and scatter. Pie, sankey, table, KPI, heatmap, boxplot, map, and HTML charts cannot be split. - **Panel labels are not configurable.** Each panel is labeled with its dimension value; the font, size, and color are fixed. - **The split is enabled on a single-view chart.** A chart that already carries data labels, a reference line, or a second Y axis series cannot be split — turn the split on first. The order is the only constraint: once a chart is split, data labels and reference lines can be added freely and are drawn in every panel. diff --git a/docs-mintlify/docs/explore-analyze/charts/configuration/tooltips.mdx b/docs-mintlify/docs/explore-analyze/charts/configuration/tooltips.mdx index 1f4ec0b5956d6..33db7523c45b1 100644 --- a/docs-mintlify/docs/explore-analyze/charts/configuration/tooltips.mdx +++ b/docs-mintlify/docs/explore-analyze/charts/configuration/tooltips.mdx @@ -9,7 +9,9 @@ Tooltips appear when a user hovers over a data point in a chart. By default, Cub ## Default behavior -When a chart is first created, all fields assigned to the chart (X, Y, color, size) are included in the tooltip automatically. The tooltip is enabled by default on all Vega-based chart types (bar, line, area, scatter, heatmap, boxplot) and on map charts. +When a chart is first created, all fields assigned to the chart (X, Y, color, size) are included in the tooltip automatically. The tooltip is enabled by default on all Vega-based chart types (bar, line, area, scatter, heatmap, boxplot), on map charts, and on sankey charts. + +[Sankey](/docs/explore-analyze/charts/chart-types/sankey) is the exception to the first rule: it seeds the tooltip with every column *except* the source, target and value it already draws. ## Configuring tooltip fields diff --git a/docs-mintlify/docs/explore-analyze/explore.mdx b/docs-mintlify/docs/explore-analyze/explore.mdx index 44e29e7216cc6..44078c278f8b0 100644 --- a/docs-mintlify/docs/explore-analyze/explore.mdx +++ b/docs-mintlify/docs/explore-analyze/explore.mdx @@ -50,7 +50,11 @@ Explore results carry the same [freshness and pre-aggregation indicators](/docs/ ## Saving explorations You can save an exploration so you can return to it later without -re-building your query. +re-building your query. An exploration is the same kind of object as a +workbook [report](/docs/explore-analyze/workbooks) — the only difference is +whether it belongs to a workbook. [Analytics Chat](/docs/explore-analyze/analytics-chat) +and the Cube MCP `createReport` tool save a standalone exploration the same +way: by leaving the workbook unset. - **Save** — For an unsaved exploration, click **Save** to open the **Save Exploration** dialog. Give your exploration a name and click diff --git a/docs-mintlify/docs/explore-analyze/workbooks/index.mdx b/docs-mintlify/docs/explore-analyze/workbooks/index.mdx index 390915ad0af6b..e1f43bc5bc698 100644 --- a/docs-mintlify/docs/explore-analyze/workbooks/index.mdx +++ b/docs-mintlify/docs/explore-analyze/workbooks/index.mdx @@ -6,6 +6,9 @@ title: Workbooks Workbooks allow you to build reports and explore data with AI agents, organize the results of your analysis, and share trusted insights with your team. +A report inside a workbook and a standalone [exploration](/docs/explore-analyze/explore#saving-explorations) +are the same kind of object — the only difference is whether it's filed inside a workbook. + ## Tabs Workbooks can contain one or more tabs, each providing a different way to query and analyze your data. Tabs enable you to organize multiple analyses within a single workbook, making it easy to explore different aspects of your data or combine insights from different sources. diff --git a/docs-mintlify/docs/explore-analyze/workbooks/python-analysis.mdx b/docs-mintlify/docs/explore-analyze/workbooks/python-analysis.mdx index 74daa648ae53f..2d54566a87f78 100644 --- a/docs-mintlify/docs/explore-analyze/workbooks/python-analysis.mdx +++ b/docs-mintlify/docs/explore-analyze/workbooks/python-analysis.mdx @@ -32,9 +32,10 @@ Ask for the analysis in natural language — "forecast next quarter's revenue", result inline in the [chat thread](/docs/explore-analyze/analytics-chat). This result is ephemeral by default. -Ask to **save it to a workbook** and Cube persists both the code and the run result -onto a report, which then renders the saved output without re-running. Saving -re-executes the analysis. +Ask to **save it** — to a workbook, or as a standalone +[exploration](/docs/explore-analyze/explore#saving-explorations) — and Cube persists +both the code and the run result. Opening the saved copy renders that output without +re-running; saving re-executes the analysis. diff --git a/docs-mintlify/docs/explore-analyze/workbooks/querying-data.mdx b/docs-mintlify/docs/explore-analyze/workbooks/querying-data.mdx index a49462a39596f..8fe236eca2726 100644 --- a/docs-mintlify/docs/explore-analyze/workbooks/querying-data.mdx +++ b/docs-mintlify/docs/explore-analyze/workbooks/querying-data.mdx @@ -105,7 +105,9 @@ above cannot express. ### Default filters A data model author can define default workbook filters on a semantic view with -the [`meta.default_ui_filters`][ref-default-ui-filters] parameter: +the [`meta.default_ui_filters`][ref-default-ui-filters] parameter. The same +defaults are also seeded when a query on that view is started in the Google +Sheets / Excel add-in. ```yaml views: diff --git a/docs-mintlify/docs/integrations/google-sheets.mdx b/docs-mintlify/docs/integrations/google-sheets.mdx index 54f03dae5493d..9ae74899c7501 100644 --- a/docs-mintlify/docs/integrations/google-sheets.mdx +++ b/docs-mintlify/docs/integrations/google-sheets.mdx @@ -73,10 +73,15 @@ looks and feels like [Playground][ref-playground]. -Google Cloud for Sheets works only with [views][ref-views], not cubes. +Cube Cloud for Sheets works only with [views][ref-views], not cubes. +If the view defines [`default_ui_filters`][ref-default-ui-filters], those +filters are pre-populated as soon as you select the view — the same way they +are in workbooks. They are a starting point, not enforcement: you can change +their values, switch operators, or remove them. + Click on members to add them to **Rows** and **Measures**. If needed, drag dimensions from **Rows** to **Columns**. Click on the funnel buttons to add members to **Filters**. Click on **×** to @@ -100,6 +105,31 @@ With every change to your query, Cube Cloud for Sheets will update the report on the sheet after a slight delay. If you'd like to minimize it, consider implementing [pre-aggregations][ref-pre-aggs]. +### Measure position and order + +By default, measures nest under each value of the dimension they're paired +with — every measure for the first column value, then every measure for the +next. Open the **Display** tab to change **Measure position** to **Before +columns** to get the opposite layout: each measure spans every column value, +with all values of one measure together before moving to the next. + +For example, with `Forecast Sales Units` and `Forecast Net Sales` on Measures +and a `season` dimension on Columns: + +- **After columns** (default): `Q1: [Sales Units, Net Sales] | Q2: [Sales + Units, Net Sales]` +- **Before columns**: `Sales Units: [Q1, Q2] | Net Sales: [Q1, Q2]` + +{/* TODO: screenshot — side-by-side of the two output layouts (After columns vs Before columns) */} + +The same choice applies when measures are placed on **Rows** instead (set +with **Measures on**, in the same tab) — there it's labeled **After rows** / +**Before rows**. + +To change the order measures appear in within their group, drag them within +the **Measures** pane on the **Pivot** tab. Position and order are saved with +the report and survive **Refresh**. + When your report is ready, you can optionally move it to [saved reports](#work-with-saved-reports) by clicking **Save**. @@ -111,8 +141,20 @@ exploration by name across your whole deployment, not just the current folder — results are grouped by type and show each item's folder location, and selecting one navigates you straight to it. -Click **Refresh** to manually refresh the data in the report's location. -Click **Edit** to change the query or the location. +An exploration can be placed more than once — on different sheets or at +different anchors in the same spreadsheet, and in more than one document at +once (a Google Sheets spreadsheet and an Excel workbook simultaneously). +Hovering a row in this list shows every placement it has in the **current** +document under **Location** / **Locations**. Click **Refresh** to update all +of that exploration's placements in the current document at once; click its +title to open it and change the query, which applies to every placement. + +A placement survives renaming the sheet it's on or the spreadsheet it's in — +it's tracked by the sheet's and spreadsheet's own stable ids, not by name. A +placement's anchor is a fixed cell reference, though, so it does **not** +survive rows or columns inserted above it: the visible data shifts down with +the insert, but the stored anchor doesn't move with it, so the next refresh +targets the wrong cell. @@ -133,5 +175,6 @@ in Cube Cloud. [ref-playground]: /docs/explore-analyze/playground [ref-views]: /docs/data-modeling/views [ref-pre-aggs]: /docs/pre-aggregations/using-pre-aggregations +[ref-default-ui-filters]: /reference/data-modeling/view#default_ui_filters [ref-sql-api-enabled]: /reference/core-data-apis/sql-api#cube-cloud -[ref-saved-reports]: /docs/workspace/saved-reports \ No newline at end of file +[ref-saved-reports]: /docs/workspace/saved-reports diff --git a/docs-mintlify/docs/integrations/mcp-server.mdx b/docs-mintlify/docs/integrations/mcp-server.mdx index e69485ee866b4..8db7500f06543 100644 --- a/docs-mintlify/docs/integrations/mcp-server.mdx +++ b/docs-mintlify/docs/integrations/mcp-server.mdx @@ -239,11 +239,17 @@ work together. | Tool | Description | Access | | --- | --- | --- | | `searchDataModel` | Searches the semantic model by similarity — views and their measures and dimensions — to discover what is queryable. Returns compact records (name, title, description, type) to reference in `runQuery`. | Read-only | -| `runQuery` | Runs a Cube SQL query (PostgreSQL dialect) against the [SQL API][ref-sql-api]. Returns a schema, a page of rows, and `hasMore` / `totalRows` for pagination via `offset`. | Read-only | +| `runQuery` | Runs a Cube SQL query (PostgreSQL dialect) against the [SQL API][ref-sql-api]. Returns a schema, a page of rows, and `hasMore` / `totalRows` for pagination via `offset`. Pass the dev `branchName` from `startDataModelEdit` to query that branch instead of the deployed model. | Read-only | Call `searchDataModel` before `runQuery` to find exact view and member names rather than guessing them. +Omitting `branchName` queries the deployed model, exactly as before this parameter existed. +Passing a `branchName` that isn't an active dev branch — for example the deploy branch's own +name, or a feature branch with no dev worker running — fails with an error rather than +silently answering from the deployed model, so a query aimed at the wrong branch never +looks like a query that just returned no rows. + ### Dashboard authoring These tools build [workbooks][ref-workbooks] and [dashboards][ref-dashboards] @@ -253,7 +259,7 @@ programmatically. Creating and editing workbooks requires the Explorer role or h | --- | --- | --- | | `readWorkbook` | Reads a workbook — its name and its current dashboard draft and published configs. | Read-only | | `createWorkbook` | Creates a new empty workbook, the container that holds reports and a dashboard. | Write | -| `createReport` | Saves a query plus its visualization as a report inside a workbook, and returns the `reportId` a chart widget references. | Write | +| `createReport` | Saves a query plus its visualization as a report. Pass `workbookId` to file it inside a workbook and get the `reportId` a chart widget references; omit `workbookId` to save a standalone [exploration](/docs/explore-analyze/explore#saving-explorations) instead. | Write | | `readReport` | Reads one saved report by id — its title, SQL, chart spec, placement, and a shareable URL. | Read-only | | `updateReport` | Edits an existing report in place, keeping its `reportId`. Send only the fields you want to change. | Destructive — prompts | | `deleteReport` | Deletes a report. | Destructive — prompts | @@ -320,6 +326,9 @@ into the MCP server: model-editing tools, `getBranchDiff`, `getDeploymentEnv`, and both pre-aggregation tools — is offered only to users whose role allows editing the semantic model. A Viewer never sees them at all. +- **Verification happens on the branch, before a merge, not after.** Pass the dev + `branchName` to `runQuery` to check that an edit returns the right data — `valid: true` + from `writeDataModelFile` only means the model compiles, not that it's correct. Review pending work with `getDataModelChanges` before you commit. @@ -400,6 +409,8 @@ Call `startDataModelEdit` to enter development mode and get a dev `branchName`. the current source with `listDataModelFiles` and `readDataModelFile`, then apply changes with `writeDataModelFile` or `deleteDataModelFile`, passing that `branchName`. Each write recompiles the model and reports validation errors, so you can iterate until it compiles. +Before committing, verify the change returns the right data: call `runQuery` with the same +`branchName` and check the numbers, since a compiling model can still return wrong results. Review the result with `getDataModelChanges`, then commit the branch from the Cube UI to publish it. diff --git a/docs-mintlify/docs/integrations/microsoft-excel.mdx b/docs-mintlify/docs/integrations/microsoft-excel.mdx index cb658fb1303b7..4024888e8402d 100644 --- a/docs-mintlify/docs/integrations/microsoft-excel.mdx +++ b/docs-mintlify/docs/integrations/microsoft-excel.mdx @@ -85,6 +85,11 @@ Cube Cloud for Excel works only with [views][ref-views], not cubes. +If the view defines [`default_ui_filters`][ref-default-ui-filters], those +filters are pre-populated as soon as you select the view — the same way they +are in workbooks. They are a starting point, not enforcement: you can change +their values, switch operators, or remove them. + Click on members to add them to **Rows** and **Measures**. If needed, drag dimensions from **Rows** to **Columns**. Click on the funnel buttons to add members to **Filters**. Click on **×** to @@ -108,6 +113,31 @@ With every change to your query, Cube Cloud for Excel will update the report on the sheet after a slight delay. If you'd like to minimize it, consider implementing [pre-aggregations][ref-pre-aggs]. +### Measure position and order + +By default, measures nest under each value of the dimension they're paired +with — every measure for the first column value, then every measure for the +next. Open the **Display** tab to change **Measure position** to **Before +columns** to get the opposite layout: each measure spans every column value, +with all values of one measure together before moving to the next. + +For example, with `Forecast Sales Units` and `Forecast Net Sales` on Measures +and a `season` dimension on Columns: + +- **After columns** (default): `Q1: [Sales Units, Net Sales] | Q2: [Sales + Units, Net Sales]` +- **Before columns**: `Sales Units: [Q1, Q2] | Net Sales: [Q1, Q2]` + +{/* TODO: screenshot — side-by-side of the two output layouts (After columns vs Before columns) */} + +The same choice applies when measures are placed on **Rows** instead (set +with **Measures on**, in the same tab) — there it's labeled **After rows** / +**Before rows**. + +To change the order measures appear in within their group, drag them within +the **Measures** pane on the **Pivot** tab. Position and order are saved with +the report and survive **Refresh**. + When your report is ready, you can optionally move it to [saved reports](#work-with-saved-reports) by clicking **Save**. @@ -119,8 +149,20 @@ exploration by name across your whole deployment, not just the current folder — results are grouped by type and show each item's folder location, and selecting one navigates you straight to it. -Click **Refresh** to manually refresh the data in the report's location. -Click **Edit** to change the query or the location. +An exploration can be placed more than once — on different sheets or at +different anchors in the same workbook, and in more than one document at +once (a Google Sheets spreadsheet and an Excel workbook simultaneously). +Hovering a row in this list shows every placement it has in the **current** +document under **Location** / **Locations**. Click **Refresh** to update all +of that exploration's placements in the current document at once; click its +title to open it and change the query, which applies to every placement. + +A placement survives renaming the sheet it's on or the workbook it's in — +it's tracked by the sheet's and workbook's own stable ids, not by name. A +placement's anchor is a fixed cell reference, though, so it does **not** +survive rows or columns inserted above it: the visible data shifts down with +the insert, but the stored anchor doesn't move with it, so the next refresh +targets the wrong cell. @@ -141,6 +183,7 @@ in Cube Cloud. [ref-playground]: /docs/explore-analyze/playground [ref-views]: /docs/data-modeling/views [ref-pre-aggs]: /docs/pre-aggregations/using-pre-aggregations +[ref-default-ui-filters]: /reference/data-modeling/view#default_ui_filters [ref-sql-api-enabled]: /reference/core-data-apis/sql-api#cube-cloud [link-excel-addins]: https://support.microsoft.com/en-us/office/add-or-remove-add-ins-in-excel-0af570c4-5cf3-4fa9-9b88-403625a0b460 [link-ms-appsource]: https://appsource.microsoft.com/en-us/product/office/WA200008486 diff --git a/docs-mintlify/embedding/iframe/creator-mode.mdx b/docs-mintlify/embedding/iframe/creator-mode.mdx index d4d5f49736c96..07694a34c25c6 100644 --- a/docs-mintlify/embedding/iframe/creator-mode.mdx +++ b/docs-mintlify/embedding/iframe/creator-mode.mdx @@ -15,6 +15,13 @@ To enable it, pass `creatorMode: true` to the [Generate Session API][ref-generat Creator Mode requires [Signed embedding](/embedding/iframe/auth/signed). Private embedding is not supported. +## Dashboard header controls + +When a user opens a dashboard inside Creator Mode, its header renders the +same as in a standalone dashboard embed. To hide any of its controls (title, +back button, Edit, Duplicate) for your embed, see +[Show or hide header controls](/embedding/iframe/dashboards#show-or-hide-header-controls). + ## Embed tenant scoping In Creator Mode, content (workbooks, dashboards) and the groups/user attributes referenced by the session are scoped to an **embed tenant**. Pass `embedTenantName` to isolate content per customer; omit it to use the current tenant. diff --git a/docs-mintlify/embedding/iframe/dashboards.mdx b/docs-mintlify/embedding/iframe/dashboards.mdx index 905fd42e34e74..955f950b23a79 100644 --- a/docs-mintlify/embedding/iframe/dashboards.mdx +++ b/docs-mintlify/embedding/iframe/dashboards.mdx @@ -77,20 +77,27 @@ This works on both regular and published (embedded) dashboards. The filter is only applied if a matching filter widget for that dimension already exists on the dashboard. -## Allow CSV export +## Allow chart export {#allow-csv-export} By default, embedded dashboards do not expose a download action on individual -widgets. To let viewers download a chart widget's data as a CSV file, add the -`allowExport=true` query parameter to the embed URL: +widgets. To let viewers download a chart widget as a **CSV**, **PNG**, or +**PDF** file, add the `allowExport=true` query parameter to the embed URL: ```text https://your-tenant.cubecloud.dev/embed/dashboard/YOUR_DASHBOARD_PUBLIC_ID?session=YOUR_SESSION_ID&allowExport=true ``` -When enabled, each chart widget's ⋮ menu shows a **Download as CSV** action. The -CSV is generated client-side from the data already loaded into the widget, so no -additional query is issued. The parameter is opt-in — omit it (the default) to -keep the download action hidden. +When enabled, each chart widget's ⋮ menu shows **Download as CSV**, **Download +as PNG**, and **Download as PDF** actions. `allowExport` is the single switch +for all three formats — there is no way to allow one format while blocking +another. Only the exact literal string `true` opts in: `allowExport=1`, +`allowExport=TRUE`, a bare `?allowExport`, and omitting the parameter (the +default) all leave every download action hidden. + +The CSV is generated client-side from the data already loaded into the +widget, so no additional query is issued. Inside an embed, `allowExport` — +not the viewer's own Cube role — is the authority on whether the download +actions appear. ## Show or hide the AI chat @@ -122,6 +129,49 @@ This applies to embedded published dashboards; it does not affect the standalone [Analytics Chat](/embedding/iframe/analytics-chat) surface, where embedding the chat is itself the opt-in. +## Show or hide header controls + +By default, an embedded dashboard's header shows its title, back button, and +— when the viewer has permission — the Edit and Duplicate actions. Hide any +of them individually with URL parameters on the embed iframe `src`: + +| Parameter | Hides | +| --- | --- | +| `showDashboardHeader=false` | The entire header bar | +| `showDashboardBackButton=false` | The back button | +| `showDashboardTitle=false` | The dashboard title | +| `showDashboardEditButton=false` | The Edit action | +| `showDashboardDuplicateButton=false` | The Duplicate action | + +```text +https://your-tenant.cubecloud.dev/embed/dashboard/YOUR_DASHBOARD_PUBLIC_ID?session=YOUR_SESSION_ID&showDashboardBackButton=false&showDashboardTitle=false +``` + +Every one of these defaults to shown. Only the exact literal string `false` +hides a control — `=0`, `=False`, `=FALSE`, and a bare `?showDashboardTitle` +(no value) all leave it visible. Note this is the inverse of `allowExport` +above, where only the literal string `true` opts in. + + +These parameters are URL-only — they aren't accepted in the [Generate +Session](/reference/embed-apis/generate-session#session-settings) `settings` +object, unlike `showDashboardChat`, and they have no account-wide default +under **Embed → Settings**. + + +These parameters apply to the dashboard header only — the workbook header +(which carries Publish and Share) is unaffected. + +Setting `showDashboardHeader=false` overrides the other four: the whole bar +disappears regardless of their values. `showDashboardEditButton` can only +hide the Edit action; it can never show one to a viewer who lacks edit +permission. + +These parameters are read from the URL the host loaded the iframe with and +stay pinned for the life of the embed, so they survive in-app navigation +(for example, from the home-page dashboard card, or after publishing a +draft). + ## Set the language Embedded dashboards render their UI in the account's default language, which you can diff --git a/docs-mintlify/embedding/iframe/events.mdx b/docs-mintlify/embedding/iframe/events.mdx index 066816c5c9964..25c78576972f9 100644 --- a/docs-mintlify/embedding/iframe/events.mdx +++ b/docs-mintlify/embedding/iframe/events.mdx @@ -186,9 +186,9 @@ painted" signal (distinct from `ready`, which fires at mount, before data loads) #### `cube:event:download` {#cube-event-download} -Emitted when a viewer exports something — a widget's data as CSV, or (in future) -a dashboard image. Reports _that_ an export happened and its shape — never the -exported rows themselves. +Emitted when a viewer exports something — a widget's data as CSV, or a widget +as a PNG or PDF image. Reports _that_ an export happened and its shape — never +the exported rows themselves. | Field | Type | Description | | --- | --- | --- | @@ -209,10 +209,10 @@ exported rows themselves. ``` - The CSV download action on a dashboard widget only appears when the embed URL - includes `allowExport=true` (see [Dashboards → Allow CSV + A dashboard widget's download actions (CSV, PNG, PDF) only appear when the + embed URL includes `allowExport=true` (see [Dashboards → Allow chart export](/embedding/iframe/dashboards#allow-csv-export)). The event fires when a - viewer uses it. + viewer uses one of them. #### `cube:event:drilldown` {#cube-event-drilldown} diff --git a/docs-mintlify/recipes/data-modeling/cross-data-source-queries.mdx b/docs-mintlify/recipes/data-modeling/cross-data-source-queries.mdx new file mode 100644 index 0000000000000..52016aa151268 --- /dev/null +++ b/docs-mintlify/recipes/data-modeling/cross-data-source-queries.mdx @@ -0,0 +1,368 @@ +--- +title: Querying across data sources +description: Append rows from cubes that live in different databases into one result set with a UNION ALL query through the SQL API. +--- + +## Use case + +Some datasets are spread across more than one database. Product events, for +example, are often split by age: the last few months stay in a fast analytical +database that serves live dashboards, while everything older is moved to +cheaper storage. Both tables describe the same events and carry the same +columns. + +The goal is to report on them together — one result set with the rows of both +databases appended, plus a dimension that says which database each row came +from. + +This is a *union*: it adds rows. It is a different problem from a +[`rollup_join`](/reference/data-modeling/pre-aggregations#rollup_join), which +adds columns by relating entities that live in different databases. It is also +different from [data blending](/docs/data-modeling/concepts/data-blending), +which unions cubes inside a single database. + +The [SQL API](/reference/core-data-apis/sql-api) can do this at query time, +against live data and without pre-aggregations. Each cube is queried on its own +data source, and Cube appends the results. + +## Configuration + +Define the two connections as [multiple data +sources](/admin/connect-to-data/multiple-data-sources). The default source needs +no name; every other source gets one, and the full list goes in +`CUBEJS_DATASOURCES`: + +```dotenv +CUBEJS_DATASOURCES=default,recent + +CUBEJS_DB_TYPE=postgres +CUBEJS_DB_HOST=archive.example.com +CUBEJS_DB_NAME=analytics +# ... + +CUBEJS_DS_RECENT_DB_TYPE=clickhouse +CUBEJS_DS_RECENT_DB_HOST=clickhouse.example.com +CUBEJS_DS_RECENT_DB_NAME=analytics +# ... +``` + +## Data modeling + +Model each table as its own cube, and point one of them at the named data source +with [`data_source`](/reference/data-modeling/cube#data_source). The cube without +a `data_source` uses the default one. + +Give both cubes a matching set of members, and add a constant dimension that +identifies the origin of each row. That dimension is what makes the two halves +of the union distinguishable once they sit in the same result set: + + + +```yaml title="YAML" +cubes: + - name: archived_events + sql_table: events + + dimensions: + - name: id + sql: id + type: number + primary_key: true + + - name: created_at + sql: created_at + type: time + + - name: tier + sql: tier + type: string + + - name: storage + sql: "'archive'" + type: string + + measures: + - name: event_count + type: count + + - name: recent_events + sql_table: events + data_source: recent + + dimensions: + - name: id + sql: id + type: number + primary_key: true + + - name: created_at + sql: created_at + type: time + + - name: tier + sql: tier + type: string + + - name: storage + sql: "'recent'" + type: string + + measures: + - name: event_count + type: count +``` + +```javascript title="JavaScript" +cube(`archived_events`, { + sql_table: `events`, + + dimensions: { + id: { + sql: `id`, + type: `number`, + primary_key: true + }, + + created_at: { + sql: `created_at`, + type: `time` + }, + + tier: { + sql: `tier`, + type: `string` + }, + + storage: { + sql: `'archive'`, + type: `string` + } + }, + + measures: { + event_count: { + type: `count` + } + } +}) + +cube(`recent_events`, { + sql_table: `events`, + data_source: `recent`, + + dimensions: { + id: { + sql: `id`, + type: `number`, + primary_key: true + }, + + created_at: { + sql: `created_at`, + type: `time` + }, + + tier: { + sql: `tier`, + type: `string` + }, + + storage: { + sql: `'recent'`, + type: `string` + } + }, + + measures: { + event_count: { + type: `count` + } + } +}) +``` + + + +## Querying + +Connect to the [SQL API](/reference/core-data-apis/sql-api) and append the two +cubes with `UNION ALL`: + +```sql +SELECT storage, tier, MEASURE(event_count) AS events +FROM archived_events +GROUP BY 1, 2 +UNION ALL +SELECT storage, tier, MEASURE(event_count) AS events +FROM recent_events +GROUP BY 1, 2 +``` + +``` + storage | tier | events +---------+------------+-------- + archive | free | 412508 + archive | enterprise | 95012 + recent | free | 38471 + recent | enterprise | 9930 +``` + +Each half of the union is evaluated against its own database, in that database's +own dialect, and Cube appends the two results. Filters reach the databases +rather than being applied afterwards, so a `WHERE` clause on either side limits +what that database scans: + +```sql +SELECT storage, tier, MEASURE(event_count) AS events +FROM archived_events +WHERE tier = 'enterprise' +GROUP BY 1, 2 +UNION ALL +SELECT storage, tier, MEASURE(event_count) AS events +FROM recent_events +WHERE tier = 'enterprise' +GROUP BY 1, 2 +``` + +Add a time dimension to both halves to line the databases up on a common grain: + +```sql +SELECT storage, tier, DATE_TRUNC('day', created_at) AS date, MEASURE(event_count) AS events +FROM archived_events +GROUP BY 1, 2, 3 +UNION ALL +SELECT storage, tier, DATE_TRUNC('day', created_at) AS date, MEASURE(event_count) AS events +FROM recent_events +GROUP BY 1, 2, 3 +ORDER BY 3, 1 +``` + + + +A cube whose name starts with `pg_` cannot be referenced by that name alone. The +SQL API routes such a name to `pg_catalog`, where no cube is ever found, and the +query fails with `Table or CTE with name 'pg_...' not found`. Qualify it with the +schema that holds cubes, as in `FROM public.pg_costs`, or avoid the prefix. + + + +### Aggregating across the union + +Wrap the union in a CTE to aggregate over both databases at once. Here the +per-tier totals combine events from both databases: + +```sql +WITH blended AS ( + SELECT storage, tier, MEASURE(event_count) AS events + FROM archived_events + GROUP BY 1, 2 + UNION ALL + SELECT storage, tier, MEASURE(event_count) AS events + FROM recent_events + GROUP BY 1, 2 +) +SELECT tier, SUM(events) AS total, COUNT(DISTINCT storage) AS databases +FROM blended +GROUP BY 1 +ORDER BY 2 DESC +``` + +``` + tier | total | databases +------------+--------+----------- + free | 450979 | 2 + enterprise | 104942 | 2 +``` + +`UNION` also works where duplicate rows should collapse, as do an outer +`ORDER BY` and `LIMIT` over the union. + + + +Aggregating over the union like this is only correct for additive measures such +as `count` and `sum`. Non-additive measures — `count_distinct`, `avg`, +percentiles — cannot be combined from per-source results: summing distinct +counts double-counts anything present in both databases, and averaging averages +ignores how many rows each database contributed. No error is raised, so report +these measures per data source instead. + + + +### Inspecting how a query is split + +Run `EXPLAIN` on any of these queries to see the plan. It puts a `Union` over one +`CubeScan` per cube, and each scan carries its own filter — the `WHERE` clause is +part of the request sent for that cube, not a step applied after the results are +appended: + +```sql +EXPLAIN SELECT storage, tier, MEASURE(event_count) AS events +FROM archived_events +WHERE tier = 'enterprise' +GROUP BY 1, 2 +UNION ALL +SELECT storage, tier, MEASURE(event_count) AS events +FROM recent_events +WHERE tier = 'enterprise' +GROUP BY 1, 2 +``` + +``` +Union + CubeScan: request={ + "measures": [ + "archived_events.event_count" + ], + "dimensions": [ + "archived_events.storage", + "archived_events.tier" + ], + "segments": [], + "order": [], + "filters": [ + { + "member": "archived_events.tier", + "operator": "equals", + "values": [ + "enterprise" + ] + } + ] +} + CubeScan: request={ + "measures": [ + "recent_events.event_count" + ], + "dimensions": [ + "recent_events.storage", + "recent_events.tier" + ], + "segments": [], + "order": [], + "filters": [ + { + "member": "recent_events.tier", + "operator": "equals", + "values": [ + "enterprise" + ] + } + ] +} +``` + +The plan identifies each scan by cube, not by data source, so read it together +with the `data_source` of each cube to see which database serves which half. + +## Limitations + +Unions combine rows across data sources; joins do not. A query that joins two +cubes on different data sources is rejected, and relating entities across +databases needs a +[`rollup_join`](/reference/data-modeling/pre-aggregations#rollup_join) instead. + +Each half of the union is subject to the [maximum row +limit](/docs/data-modeling/configuration#maximum-row-limit) on its own, and the +cap applies before the results are appended. Aggregate inside each half of the +union, as in the examples above, rather than unioning raw rows and aggregating +afterwards. diff --git a/docs-mintlify/recipes/index.mdx b/docs-mintlify/recipes/index.mdx index 469eb137d4509..4462abde37e77 100644 --- a/docs-mintlify/recipes/index.mdx +++ b/docs-mintlify/recipes/index.mdx @@ -4,7 +4,7 @@ description: Step-by-step tutorials and best practices for getting the most out mode: wide --- -Explore **39 recipes** across data modeling, calculations, analytics patterns, +Explore **41 recipes** across data modeling, calculations, analytics patterns, pre-aggregations, configuration, APIs, and AI. ## Data Modeling @@ -28,6 +28,9 @@ pre-aggregations, configuration, APIs, and AI. Combine multiple database tables that relate to the same entity into a single cube. + + Append rows from cubes in different databases into one result set with a UNION ALL query. + Define a custom sort order for categorical values like pipeline stages that don't sort alphabetically. diff --git a/docs-mintlify/reference/data-modeling/view.mdx b/docs-mintlify/reference/data-modeling/view.mdx index 29920e8e454a7..f5a3731f04bb9 100644 --- a/docs-mintlify/reference/data-modeling/view.mdx +++ b/docs-mintlify/reference/data-modeling/view.mdx @@ -287,10 +287,11 @@ over the view's default. When `auto_run` is not set, auto-run is on. #### `default_ui_filters` In the Cube cloud platform, you can use the `default_ui_filters` key in a -view's `meta` to define default filters for [workbooks][ref-workbook-filtering] -and views embedded via the React Embed SDK. When the view is selected, these -filters are pre-populated as regular, fully editable filters — data consumers -can change their values, switch operators, or remove them entirely. +view's `meta` to define default filters for [workbooks][ref-workbook-filtering], +views embedded via the React Embed SDK, and the Google Sheets / Excel add-in. +When the view is selected, these filters are pre-populated as regular, fully +editable filters — data consumers can change their values, switch operators, +or remove them entirely. Each entry has a `member` (a dimension or measure exposed by the view), an `operator`, and a list of `values` — or a single `value`, which is natural for @@ -418,11 +419,11 @@ bounds, drops that filter entry with a browser-console warning — the remaining entries still apply, and view selection never breaks. Unlike [`default_filters`](#default_filters), which are enforced on every query -against the view, `default_ui_filters` only provide a starting point for the -workbook UI: they don't apply to queries made through APIs or other -visualization tools, and workbook users can freely modify or remove them. -They also only apply to new queries — defining or changing them in the data -model does not add them to existing queries. +against the view, `default_ui_filters` only provide a starting point: they +don't apply to queries made through APIs or other visualization tools, and +data consumers can freely modify or remove them. They also only apply to new +queries — defining or changing them in the data model does not add them to +existing queries or saved explorations. ### `cubes` diff --git a/docs-mintlify/reference/embed-apis/generate-session.mdx b/docs-mintlify/reference/embed-apis/generate-session.mdx index e0f1bc0c73782..bb7454defa8e4 100644 --- a/docs-mintlify/reference/embed-apis/generate-session.mdx +++ b/docs-mintlify/reference/embed-apis/generate-session.mdx @@ -79,6 +79,10 @@ over the account-wide setting. |----------|------|----------|-------| | `showDashboardChat` | boolean | No | Show or hide the AI chat (agent panel and launcher bubble) on embedded dashboards for this session. Omit to inherit the account-wide **Show dashboard chat** toggle (**Embed → Settings**; shown by default); `false` hides it even if enabled account-wide, `true` shows it even if disabled. Affects embedded **published dashboards** only — not the standalone [embedded chat][ref-analytics-chat] surface. See [Hiding the AI chat][ref-signed-embedding]. | + +The dashboard header's title, back button, Edit, and Duplicate controls are hidden with URL parameters on the embed iframe instead — they aren't part of this `settings` object. See [Show or hide header controls](/embedding/iframe/dashboards#show-or-hide-header-controls). + + ## User profile `userProfile` lets you attach a human-readable display name and avatar to an external user so they render with a recognizable identity inside embedded surfaces (workbook owners, dashboard headers, etc.) instead of the raw `externalId`. diff --git a/docs-mintlify/scripts/extract-api.mjs b/docs-mintlify/scripts/extract-api.mjs index e6929d74b470c..028419e20a03f 100644 --- a/docs-mintlify/scripts/extract-api.mjs +++ b/docs-mintlify/scripts/extract-api.mjs @@ -168,12 +168,27 @@ const EXCLUDE_OPERATIONS = new Set([ 'PUT /api/v1/resource-policies/group', 'PUT /api/v1/resource-policies/user', 'GET /api/v1/app-theme', - 'GET /api/v1/ai-engineer/active-region', 'GET /api/v1/ai-engineer/settings', // Report folders listing — not part of the public docs surface. 'GET /api/v1/deployments/{deploymentId}/report-folders', ]); +// Cube-staff-only operations (provisioning real cloud infrastructure — Regions, +// PrivateLinks, and anything else gated the same way) are excluded automatically +// rather than via a hand-maintained EXCLUDE_OPERATIONS list: console-server's +// `superAdminOnlyDescription()` (packages/console-server/src/api/middlewares/ +// validate-admin-access.ts in cubejs-enterprise) prefixes every such operation's +// OpenAPI `description` with this exact marker text before ANY other processing +// touches it, so matching on it here catches staff-only operations that didn't +// exist yet when this list was last updated — not just the ones someone +// remembered to add. No reader of these docs can call them (tenant admins are +// rejected server-side), so nothing customer-facing is lost. +// +// A short, stable anchor rather than the full marker sentence — less brittle against +// upstream rewrapping/rewording, and the residual scan below (which matches even more +// loosely) is the real safety net if this ever stops matching. +const SUPER_ADMIN_ONLY_MARKER = 'Cube super admin only'; + // Explicit display names for tags whose auto-cleaned form would be unclear or // collide. Everything else is cleaned by cleanTag() below. const TAG_MAP = { @@ -267,6 +282,8 @@ const src = yaml.load(fs.readFileSync(SRC, 'utf8')); // trailing slash — a trailing slash breaks Mintlify dev), and clean tags + // operationIds. const paths = {}; +const matchedExcludes = new Set(); +let autoExcludedCount = 0; for (const [key, val] of Object.entries(src.paths)) { if (!INCLUDE_PREFIXES.some((prefix) => key.startsWith(prefix))) continue; let newKey = key.length > 1 ? key.replace(/\/$/, '') : key; // drop trailing slash @@ -274,7 +291,15 @@ for (const [key, val] of Object.entries(src.paths)) { for (const m of METHODS) { if (!val[m]) continue; // Drop explicitly hidden operations before they reach the spec or nav. - if (EXCLUDE_OPERATIONS.has(`${m.toUpperCase()} ${newKey}`)) { + const excludeKey = `${m.toUpperCase()} ${newKey}`; + if (EXCLUDE_OPERATIONS.has(excludeKey)) { + matchedExcludes.add(excludeKey); + delete val[m]; + continue; + } + // Drop Cube-staff-only operations automatically — see SUPER_ADMIN_ONLY_MARKER above. + if (typeof val[m].description === 'string' && val[m].description.includes(SUPER_ADMIN_ONLY_MARKER)) { + autoExcludedCount++; delete val[m]; continue; } @@ -310,6 +335,59 @@ if (!Object.keys(paths).length) { process.exit(1); } +// An EXCLUDE_OPERATIONS entry that matches nothing was likely renamed or moved +// upstream — silently letting it through would re-publish whatever it was meant +// to hide (some entries there exist specifically to keep staff-only operations +// out of the public docs), and `--check` can't catch this: both the committed +// and freshly generated output would contain the leak. +const unmatchedExcludes = [...EXCLUDE_OPERATIONS].filter((op) => !matchedExcludes.has(op)); +if (unmatchedExcludes.length) { + console.error( + 'Aborting: EXCLUDE_OPERATIONS entries matched nothing (renamed upstream?):\n ' + + unmatchedExcludes.join('\n ') + ); + process.exit(1); +} + +// The SUPER_ADMIN_ONLY_MARKER auto-detection above is an exact-substring match against +// text authored in a different repo — it fails OPEN, not closed, if that text drifts +// (rewording, a dropped 🔒, a punctuation change). Two guards restore a fail-closed +// default without depending on the marker staying exact: +// +// Floor: the marker matched nothing at all. Today's spec always has staff-only +// operations, so zero is almost certainly "the marker stopped matching," not "there +// are none" — and if that ever becomes a real, deliberate zero, dropping this guard +// is a one-line edit made by a human looking at exactly this message. +if (!autoExcludedCount) { + console.error( + 'Aborting: SUPER_ADMIN_ONLY_MARKER matched no operation. Did the marker text change upstream ' + + '(packages/console-server/src/api/middlewares/validate-admin-access.ts in cubejs-enterprise)?' + ); + process.exit(1); +} +// Residual scan: catch staff-only prose that slipped past the exact marker match — +// worded more loosely than the marker itself (just "super admin"/"super-admin", not +// the full sentence) so it still fires even when SUPER_ADMIN_ONLY_MARKER no longer +// matches. Covers a *partial* drift too: if only a newly-added operation is reworded +// while existing ones keep today's wording, autoExcludedCount stays non-zero and the +// floor guard above can't catch it — this scan is what does. +// NB: "super admin"/"super-admin" specifically, not the bare 🔒 — the same lock emoji +// also opens the unrelated (and legitimately public) ADMIN_ONLY_DOC_MARKER ("🔒 Admin only."). +const leakedStaffOnly = []; +for (const [p, ops] of Object.entries(paths)) { + for (const m of METHODS) { + const text = ops[m]?.['x-mint']?.content ?? ops[m]?.description ?? ''; + if (/super[-\s]?admins?\b/i.test(text)) leakedStaffOnly.push(`${m.toUpperCase()} ${p}`); + } +} +if (leakedStaffOnly.length) { + console.error( + 'Aborting: staff-only prose survived exclusion (SUPER_ADMIN_ONLY_MARKER stopped matching?):\n ' + + leakedStaffOnly.join('\n ') + ); + process.exit(1); +} + // 2. Transitive $ref schema closure. function collectRefs(node, acc) { if (Array.isArray(node)) { node.forEach((n) => collectRefs(n, acc)); return; } @@ -403,6 +481,9 @@ const out = { writeOrCheck(OUT, yaml.dump(out, { lineWidth: 100, noRefs: true })); console.log('paths:', Object.keys(paths).length, '| schemas:', Object.keys(schemas).length, '| tags:', orderedTags.length); +if (autoExcludedCount) { + console.log(`(${autoExcludedCount} Cube-staff-only operation(s) auto-excluded via SUPER_ADMIN_ONLY_MARKER)`); +} // 5. Group operations by tag (pages in source order within a tag) and capture, // per tag, its paths + the first operation's summary — used to build both the diff --git a/lerna.json b/lerna.json index 6dd0bd7ff55f7..187c51197e30f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.7.28", + "version": "1.7.29", "npmClient": "yarn", "command": { "bootstrap": { diff --git a/packages/cubejs-api-gateway/CHANGELOG.md b/packages/cubejs-api-gateway/CHANGELOG.md index e48a266fb4417..53a4870c28c5d 100644 --- a/packages/cubejs-api-gateway/CHANGELOG.md +++ b/packages/cubejs-api-gateway/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +### Bug Fixes + +- **api-gateway:** Make pre-agg build job status per-entry and data-source aware ([#11666](https://github.com/cube-js/cube/issues/11666)) ([694955f](https://github.com/cube-js/cube/commit/694955f864375515d84d463e9e7ee89a156a4a2b)) + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/api-gateway diff --git a/packages/cubejs-api-gateway/package.json b/packages/cubejs-api-gateway/package.json index 5b3553858ac10..9e78837e07933 100644 --- a/packages/cubejs-api-gateway/package.json +++ b/packages/cubejs-api-gateway/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/api-gateway", "description": "Cube API Gateway", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,9 +27,9 @@ "dist/src/*" ], "dependencies": { - "@cubejs-backend/native": "1.7.28", - "@cubejs-backend/query-orchestrator": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/native": "1.7.29", + "@cubejs-backend/query-orchestrator": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "@ungap/structured-clone": "^0.3.4", "assert-never": "^1.4.0", "body-parser": "^1.19.0", @@ -53,7 +53,7 @@ "zod": "^4.1.13" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/express": "^4.17.21", "@types/jest": "^29", "@types/jsonwebtoken": "^9.0.2", diff --git a/packages/cubejs-athena-driver/CHANGELOG.md b/packages/cubejs-athena-driver/CHANGELOG.md index cc8ac76fe2c1d..1abbe31e6416e 100644 --- a/packages/cubejs-athena-driver/CHANGELOG.md +++ b/packages/cubejs-athena-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/athena-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/athena-driver diff --git a/packages/cubejs-athena-driver/package.json b/packages/cubejs-athena-driver/package.json index 1c49026cf01fb..41cc1a9f39a2d 100644 --- a/packages/cubejs-athena-driver/package.json +++ b/packages/cubejs-athena-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/athena-driver", "description": "Cube.js Athena database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -31,12 +31,12 @@ "dependencies": { "@aws-sdk/client-athena": "^3.22.0", "@aws-sdk/credential-providers": "^3.22.0", - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28" + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "@types/ramda": "^0.27.40", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-backend-cloud/CHANGELOG.md b/packages/cubejs-backend-cloud/CHANGELOG.md index 54bd3a3d49f02..c8ad1358b8b31 100644 --- a/packages/cubejs-backend-cloud/CHANGELOG.md +++ b/packages/cubejs-backend-cloud/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/cloud + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/cloud diff --git a/packages/cubejs-backend-cloud/package.json b/packages/cubejs-backend-cloud/package.json index d6dca569fa3cb..ed6f4baca3cdc 100644 --- a/packages/cubejs-backend-cloud/package.json +++ b/packages/cubejs-backend-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/cloud", - "version": "1.7.28", + "version": "1.7.29", "description": "Cube Cloud package", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", @@ -30,7 +30,7 @@ "devDependencies": { "@babel/core": "^7.24.5", "@babel/preset-env": "^7.24.5", - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/fs-extra": "^9.0.8", "@types/jest": "^29", "jest": "^29", @@ -38,7 +38,7 @@ }, "dependencies": { "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/shared": "1.7.29", "chokidar": "^3.5.1", "env-var": "^6.3.0", "form-data": "^4.0.0", diff --git a/packages/cubejs-backend-maven/CHANGELOG.md b/packages/cubejs-backend-maven/CHANGELOG.md index 83480ad76b2a3..09c764af28175 100644 --- a/packages/cubejs-backend-maven/CHANGELOG.md +++ b/packages/cubejs-backend-maven/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/maven + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/maven diff --git a/packages/cubejs-backend-maven/package.json b/packages/cubejs-backend-maven/package.json index af7564ecae986..4e6091b29e754 100644 --- a/packages/cubejs-backend-maven/package.json +++ b/packages/cubejs-backend-maven/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/maven", "description": "Cube.js Maven Wrapper for java dependencies downloading", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "license": "Apache-2.0", "repository": { "type": "git", @@ -31,12 +31,12 @@ "dist/src/*" ], "dependencies": { - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/shared": "1.7.29", "source-map-support": "^0.5.19", "xmlbuilder2": "^2.4.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "@types/node": "^22", "jest": "^29", diff --git a/packages/cubejs-backend-native/CHANGELOG.md b/packages/cubejs-backend-native/CHANGELOG.md index ec097487f0957..006a786d3ec99 100644 --- a/packages/cubejs-backend-native/CHANGELOG.md +++ b/packages/cubejs-backend-native/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/native + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/native diff --git a/packages/cubejs-backend-native/package.json b/packages/cubejs-backend-native/package.json index c190847902b5e..d7ecbf0f007b2 100644 --- a/packages/cubejs-backend-native/package.json +++ b/packages/cubejs-backend-native/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/native", - "version": "1.7.28", + "version": "1.7.29", "author": "Cube Dev, Inc.", "description": "Native module for Cube.js (binding to Rust codebase)", "main": "dist/js/index.js", @@ -39,7 +39,7 @@ "dist/js" ], "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "@types/node": "^22", "cargo-cp-artifact": "^0.1.9", @@ -50,8 +50,8 @@ "uuid": "^11.1.1" }, "dependencies": { - "@cubejs-backend/cubesql": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/cubesql": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "@cubejs-infra/post-installer": "^0.1.2" }, "resources": { diff --git a/packages/cubejs-backend-shared/CHANGELOG.md b/packages/cubejs-backend-shared/CHANGELOG.md index aafab09388ab8..602e4f96aa936 100644 --- a/packages/cubejs-backend-shared/CHANGELOG.md +++ b/packages/cubejs-backend-shared/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/shared + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) ### Bug Fixes diff --git a/packages/cubejs-backend-shared/package.json b/packages/cubejs-backend-shared/package.json index 9bc2af1be01cf..88722abbea5c5 100644 --- a/packages/cubejs-backend-shared/package.json +++ b/packages/cubejs-backend-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/shared", - "version": "1.7.28", + "version": "1.7.29", "description": "Shared code for Cube.js backend packages", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", @@ -27,7 +27,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/bytes": "^3.1.5", "@types/cli-progress": "^3.9.1", "@types/jest": "^29", diff --git a/packages/cubejs-base-driver/CHANGELOG.md b/packages/cubejs-base-driver/CHANGELOG.md index 351d937c8f07f..13df2b18a20fb 100644 --- a/packages/cubejs-base-driver/CHANGELOG.md +++ b/packages/cubejs-base-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/base-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/base-driver diff --git a/packages/cubejs-base-driver/package.json b/packages/cubejs-base-driver/package.json index f9eae0553b85a..45ecd5cd90988 100644 --- a/packages/cubejs-base-driver/package.json +++ b/packages/cubejs-base-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/base-driver", "description": "Cube.js Base Driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -33,11 +33,11 @@ "@aws-sdk/s3-request-presigner": "^3.49.0", "@azure/identity": "^4.4.1", "@azure/storage-blob": "^12.9.0", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/shared": "1.7.29", "@google-cloud/storage": "^7.13.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "@types/node": "^22", "jest": "^29", diff --git a/packages/cubejs-bigquery-driver/CHANGELOG.md b/packages/cubejs-bigquery-driver/CHANGELOG.md index 7babf1af08460..9e05c45b9cd73 100644 --- a/packages/cubejs-bigquery-driver/CHANGELOG.md +++ b/packages/cubejs-bigquery-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/bigquery-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/bigquery-driver diff --git a/packages/cubejs-bigquery-driver/package.json b/packages/cubejs-bigquery-driver/package.json index 65a5e9f97cc50..756f3a93aa793 100644 --- a/packages/cubejs-bigquery-driver/package.json +++ b/packages/cubejs-bigquery-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/bigquery-driver", "description": "Cube.js BigQuery database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,15 +29,15 @@ "main": "index.js", "types": "dist/src/index.d.ts", "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/shared": "1.7.29", "@google-cloud/bigquery": "^7.7.0", "@google-cloud/storage": "^7.13.0", "ramda": "^0.27.2" }, "devDependencies": { - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/testing-shared": "1.7.29", "@types/big.js": "^6.2.2", "@types/dedent": "^0.7.0", "@types/jest": "^29", diff --git a/packages/cubejs-cli/CHANGELOG.md b/packages/cubejs-cli/CHANGELOG.md index 2e1a5e567a580..acd7b7a5be87e 100644 --- a/packages/cubejs-cli/CHANGELOG.md +++ b/packages/cubejs-cli/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package cubejs-cli + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package cubejs-cli diff --git a/packages/cubejs-cli/package.json b/packages/cubejs-cli/package.json index d27f6576158c6..8d77be17508bd 100644 --- a/packages/cubejs-cli/package.json +++ b/packages/cubejs-cli/package.json @@ -2,7 +2,7 @@ "name": "cubejs-cli", "description": "Cube.js Command Line Interface", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -30,10 +30,10 @@ "LICENSE" ], "dependencies": { - "@cubejs-backend/cloud": "1.7.28", + "@cubejs-backend/cloud": "1.7.29", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "chalk": "^2.4.2", "cli-progress": "^3.10", "commander": "^2.19.0", @@ -50,8 +50,8 @@ "colors": "1.4.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/server": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/server": "1.7.29", "@oclif/command": "^1.8.0", "@types/cli-progress": "^3.8.0", "@types/cross-spawn": "^6.0.2", diff --git a/packages/cubejs-clickhouse-driver/CHANGELOG.md b/packages/cubejs-clickhouse-driver/CHANGELOG.md index fb1aa319a78f2..6a740d925fee0 100644 --- a/packages/cubejs-clickhouse-driver/CHANGELOG.md +++ b/packages/cubejs-clickhouse-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/clickhouse-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/clickhouse-driver diff --git a/packages/cubejs-clickhouse-driver/package.json b/packages/cubejs-clickhouse-driver/package.json index ab4f5da8dd6ae..dd1f05b71589f 100644 --- a/packages/cubejs-clickhouse-driver/package.json +++ b/packages/cubejs-clickhouse-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/clickhouse-driver", "description": "Cube.js ClickHouse database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,16 +29,16 @@ }, "dependencies": { "@clickhouse/client": "^1.12.0", - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "moment": "^2.24.0", "sqlstring": "^2.3.1", "uuid": "^11.1.1" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "@types/jest": "^29", "jest": "^29", "typescript": "~5.2.2" diff --git a/packages/cubejs-client-core/CHANGELOG.md b/packages/cubejs-client-core/CHANGELOG.md index 328b69dd3fb2f..9cbd8695e28aa 100644 --- a/packages/cubejs-client-core/CHANGELOG.md +++ b/packages/cubejs-client-core/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-client/core + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-client/core diff --git a/packages/cubejs-client-core/package.json b/packages/cubejs-client-core/package.json index 1865c8a7dc5d2..60d127c4f3a72 100644 --- a/packages/cubejs-client-core/package.json +++ b/packages/cubejs-client-core/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/core", - "version": "1.7.28", + "version": "1.7.29", "engines": {}, "type": "module", "repository": { @@ -58,7 +58,7 @@ ], "license": "MIT", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/d3-format": "^3", "@types/d3-time-format": "^4", "@types/moment-range": "^4.0.0", diff --git a/packages/cubejs-client-dx/CHANGELOG.md b/packages/cubejs-client-dx/CHANGELOG.md index 1067446ed856f..5d99dca457a55 100644 --- a/packages/cubejs-client-dx/CHANGELOG.md +++ b/packages/cubejs-client-dx/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-client/dx + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-client/dx diff --git a/packages/cubejs-client-dx/package.json b/packages/cubejs-client-dx/package.json index 4a9017e66120e..dc8ee50644e45 100644 --- a/packages/cubejs-client-dx/package.json +++ b/packages/cubejs-client-dx/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/dx", - "version": "1.7.28", + "version": "1.7.29", "engines": {}, "repository": { "type": "git", diff --git a/packages/cubejs-client-ngx/CHANGELOG.md b/packages/cubejs-client-ngx/CHANGELOG.md index 3c8f41be681f7..a953a5363ef19 100644 --- a/packages/cubejs-client-ngx/CHANGELOG.md +++ b/packages/cubejs-client-ngx/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-client/ngx + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-client/ngx diff --git a/packages/cubejs-client-ngx/package.json b/packages/cubejs-client-ngx/package.json index 21ac9a14d43c7..bebfbd5f60c59 100644 --- a/packages/cubejs-client-ngx/package.json +++ b/packages/cubejs-client-ngx/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/ngx", - "version": "1.7.28", + "version": "1.7.29", "author": "Cube Dev, Inc.", "engines": {}, "repository": { diff --git a/packages/cubejs-client-react/CHANGELOG.md b/packages/cubejs-client-react/CHANGELOG.md index 665061d2f0f91..5358adc9d3404 100644 --- a/packages/cubejs-client-react/CHANGELOG.md +++ b/packages/cubejs-client-react/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-client/react + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-client/react diff --git a/packages/cubejs-client-react/package.json b/packages/cubejs-client-react/package.json index f266d1ae12ffe..8d371c8bb0fd3 100644 --- a/packages/cubejs-client-react/package.json +++ b/packages/cubejs-client-react/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/react", - "version": "1.7.28", + "version": "1.7.29", "author": "Cube Dev, Inc.", "license": "MIT", "engines": {}, @@ -26,7 +26,7 @@ ], "dependencies": { "@babel/runtime": "^7.1.2", - "@cubejs-client/core": "1.7.28", + "@cubejs-client/core": "1.7.29", "core-js": "^3.6.5", "ramda": "^0.27.2" }, diff --git a/packages/cubejs-client-vue3/CHANGELOG.md b/packages/cubejs-client-vue3/CHANGELOG.md index 36375c32cf04f..cd7817677efd6 100644 --- a/packages/cubejs-client-vue3/CHANGELOG.md +++ b/packages/cubejs-client-vue3/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-client/vue3 + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-client/vue3 diff --git a/packages/cubejs-client-vue3/package.json b/packages/cubejs-client-vue3/package.json index bd4bc226b7454..b980de9651a36 100644 --- a/packages/cubejs-client-vue3/package.json +++ b/packages/cubejs-client-vue3/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/vue3", - "version": "1.7.28", + "version": "1.7.29", "engines": {}, "repository": { "type": "git", @@ -27,7 +27,7 @@ "src" ], "dependencies": { - "@cubejs-client/core": "1.7.28", + "@cubejs-client/core": "1.7.29", "ramda": "^0.27.0" }, "devDependencies": { diff --git a/packages/cubejs-client-ws-transport/CHANGELOG.md b/packages/cubejs-client-ws-transport/CHANGELOG.md index 2f971fcb9096c..711b52b2ab5f5 100644 --- a/packages/cubejs-client-ws-transport/CHANGELOG.md +++ b/packages/cubejs-client-ws-transport/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-client/ws-transport + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-client/ws-transport diff --git a/packages/cubejs-client-ws-transport/package.json b/packages/cubejs-client-ws-transport/package.json index c9494f0ea742d..0d97dd5ae88b7 100644 --- a/packages/cubejs-client-ws-transport/package.json +++ b/packages/cubejs-client-ws-transport/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/ws-transport", - "version": "1.7.28", + "version": "1.7.29", "engines": {}, "repository": { "type": "git", @@ -20,7 +20,7 @@ }, "dependencies": { "@babel/runtime": "^7.1.2", - "@cubejs-client/core": "1.7.28", + "@cubejs-client/core": "1.7.29", "core-js": "^3.6.5", "isomorphic-ws": "^4.0.1", "ws": "^7.3.1" @@ -33,7 +33,7 @@ "@babel/core": "^7.3.3", "@babel/preset-env": "^7.3.1", "@babel/preset-typescript": "^7.12.1", - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/ws": "^7.2.9", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-crate-driver/CHANGELOG.md b/packages/cubejs-crate-driver/CHANGELOG.md index 5e11febd61d87..a345375c60d97 100644 --- a/packages/cubejs-crate-driver/CHANGELOG.md +++ b/packages/cubejs-crate-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/crate-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/crate-driver diff --git a/packages/cubejs-crate-driver/package.json b/packages/cubejs-crate-driver/package.json index 1daaeb392c711..c57761ab2335d 100644 --- a/packages/cubejs-crate-driver/package.json +++ b/packages/cubejs-crate-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/crate-driver", "description": "Cube.js Crate database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,13 +29,13 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/postgres-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28" + "@cubejs-backend/postgres-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "testcontainers": "^10.28.0", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-cubestore-driver/CHANGELOG.md b/packages/cubejs-cubestore-driver/CHANGELOG.md index e0dab773a26e3..6f96fd950a826 100644 --- a/packages/cubejs-cubestore-driver/CHANGELOG.md +++ b/packages/cubejs-cubestore-driver/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +### Bug Fixes + +- **server-core:** release orchestrators evicted from the LRU ([#11661](https://github.com/cube-js/cube/issues/11661)) ([b82c58a](https://github.com/cube-js/cube/commit/b82c58aa245919a36e3d20f79105b639d0c16863)) + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) ### Bug Fixes diff --git a/packages/cubejs-cubestore-driver/package.json b/packages/cubejs-cubestore-driver/package.json index 6c06ae3a9cbb5..8d5cc4ce1ae86 100644 --- a/packages/cubejs-cubestore-driver/package.json +++ b/packages/cubejs-cubestore-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/cubestore-driver", "description": "Cube Store driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,10 +27,10 @@ "unit": "jest --coverage" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/cubestore": "1.7.28", - "@cubejs-backend/native": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/cubestore": "1.7.29", + "@cubejs-backend/native": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "csv-write-stream": "^2.0.0", "flatbuffers": "25.9.23", "fs-extra": "^9.1.0", @@ -41,7 +41,7 @@ "ws": "^7.4.3" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/csv-write-stream": "^2.0.0", "@types/jest": "^29", "@types/node": "^22", diff --git a/packages/cubejs-cubestore-driver/src/WebSocketConnection.ts b/packages/cubejs-cubestore-driver/src/WebSocketConnection.ts index 362fc11cf6ff8..f20ef98cfaeac 100644 --- a/packages/cubejs-cubestore-driver/src/WebSocketConnection.ts +++ b/packages/cubejs-cubestore-driver/src/WebSocketConnection.ts @@ -67,6 +67,9 @@ interface CubeStoreWebSocket extends WebSocket { // A failure that killed this socket and that re-sending can't fix, so the // message that caused it is rejected instead of being re-sent. fatalError: Error | null; + // Stops the heartbeat interval, which otherwise keeps a reference to this socket + // (and the socket itself alive) even when nothing else points to the connection. + teardown: () => void; } export class WebSocketConnection { @@ -88,6 +91,17 @@ export class WebSocketConnection { private cubeStoreVersion: string | null = null; + // Set by close(), and never unset: a closed connection stays open only for the + // messages already in flight and never establishes another socket. Without it + // close() does not actually close anything -- the 'close' handler re-sends + // whatever was pending over a fresh connection, and the heartbeat on that one + // keeps it (and the orchestrator holding it) alive for the life of the process. + private closed: boolean = false; + + // When close() was asked for, so the heartbeat can bound the wait for the + // messages that were still in flight at that point. + private closedAt: Date | null = null; + public constructor(url: string) { this.url = url; this.messageCounter = 1; @@ -99,6 +113,13 @@ export class WebSocketConnection { } protected async initWebSocket(): Promise { + if (this.closed) { + // Refusing here is what makes close() terminal: it covers a query issued + // after the release as well as the re-send path, whose `catch` then + // rejects the messages that were in flight instead of re-opening for them. + throw new ConnectionError('Cube Store connection is closed'); + } + if (!this.webSocket) { const headers: Record = {}; headers['x-process-id'] = getProcessUid(); @@ -115,11 +136,32 @@ export class WebSocketConnection { webSocket.ping(); } + if (this.closed) { + // A closed connection is kept only for the messages already in + // flight, and this is where that wait is both re-checked and + // bounded. It has to be bounded here rather than by the heartbeat + // check below: Cube Store keeps answering the pings of a connection + // whose query is simply never completed, so that check never fires + // and a single wedged message would keep this socket -- and this + // interval, which is what makes it reachable -- for the life of the + // process. Pinging continues meanwhile, so a connection that is + // draining legitimately is not dropped for inactivity. + if (this.closedAt && new Date().getTime() - this.closedAt.getTime() > this.noHeartBeatTimeout * 1000) { + this.abandonClose(webSocket); + } else { + this.closeIfDrained(webSocket); + } + + return; + } + if (new Date().getTime() - webSocket.lastHeartBeat.getTime() > this.noHeartBeatTimeout * 1000) { webSocket.close(); } }, 5000); + webSocket.teardown = () => clearInterval(pingInterval); + webSocket.sendAsync = async (message: Uint8Array) => new Promise((resolveSend) => { // If socket is closing this message should be resent if (webSocket.readyState !== WebSocket.OPEN) { @@ -164,11 +206,48 @@ export class WebSocketConnection { return; } + // The socket is done either way, so stop its heartbeat now rather than + // relying on a 'close' that may not follow: the interval is what keeps + // the socket, and everything reachable from it, alive. + webSocket.teardown(); + + if (this.closed) { + // Nothing to retry towards: `initWebSocket()` refuses once closed. + // Whatever was written to the socket is rejected by the 'close' + // handler, which `ws` emits after an 'error' -- the drain bound + // cannot catch it any more, since `teardown()` above has already + // stopped the interval that evaluates it. + if (webSocket === this.webSocket) { + this.webSocket = null; + } + + // A no-op once this socket has opened, and the only thing left that + // can settle `readyPromise` if it has not: `teardown()` has stopped + // the interval, the 'close' handler does not touch it, and the retry + // that used to settle it by adoption is skipped just above. A socket + // closed while still CONNECTING would otherwise leave the + // `sendMessage` awaiting it pending for good -- a request hung on + // nothing, which is worse than the failure it replaced. + reject(new ConnectionError('Cube Store connection is closed')); + + return; + } + + // Counted here rather than above the branch: the closed path does not + // attempt a connection, and this counter both paces the retries and + // spends the `maxConnectRetries` budget a genuinely cold connect needs. this.currentConnectionTry += 1; if (this.currentConnectionTry < this.maxConnectRetries) { setTimeout(async () => { - resolve(this.initWebSocket()); + // `.then(resolve, reject)` rather than `resolve(promise)`: once + // this socket's `readyPromise` has settled, `resolve` returns + // without adopting -- so a rejection handed to it is never + // observed, and an unhandled rejection is fatal under Node's + // default. The retry above cannot reject for `closed` any more, + // but a close landing between this timer being armed and firing + // still can, as can anything else `initWebSocket()` throws. + this.initWebSocket().then(resolve, reject); }, this.retryWaitTime()); } else { reject(new ConnectionError( @@ -188,7 +267,7 @@ export class WebSocketConnection { webSocket.lastHeartBeat = new Date(); }); webSocket.on('close', (code: number, reason: Buffer) => { - clearInterval(pingInterval); + webSocket.teardown(); const pending = Object.keys(webSocket.sentMessages); @@ -259,6 +338,13 @@ export class WebSocketConnection { setTimeout(async () => { try { + // Everything this re-send was scheduled for has been settled in + // the meantime, so there is nothing left to carry over and a new + // connection would just stay open on its heartbeat. + if (!Object.keys(webSocket.sentMessages).length) { + return; + } + const nextWebSocket = await this.initWebSocket(); const resent = Object.keys(webSocket.sentMessages); @@ -302,6 +388,7 @@ export class WebSocketConnection { if (httpMessage.commandType() === HttpCommand.HttpError) { resolver.reject(new QueryError(`${httpMessage.command(new HttpError())?.error()}`)); + this.closeIfDrained(webSocket); return; } @@ -311,6 +398,8 @@ export class WebSocketConnection { } catch (e) { resolver.reject(e); } + + this.closeIfDrained(webSocket); }); }); @@ -530,9 +619,78 @@ export class WebSocketConnection { return this.cubeStoreVersion ?? '0.0.0'; } + /** + * Closes the connection for good. Messages already in flight are given the + * chance to be answered -- an eviction can happen mid-query, and failing one + * that Cube Store is already working on would fail the request -- so the + * socket goes away once the last of them settles, or right away if there are + * none. + */ public close() { - if (this.webSocket) { - this.webSocket.close(); + if (!this.closed) { + this.closed = true; + // First asked for, not most recently: `release()` closes the data-source + // and external drivers separately, which is the same connection twice when + // one `CubeStoreDriver` backs both, and re-stamping would restart the bound + // each time. + this.closedAt = new Date(); + } + + // Straight away when there is nothing in flight; otherwise the heartbeat + // takes it from here, both to notice the drain finishing and to bound it. + this.closeIfDrained(this.webSocket); + } + + /** + * Gives up on the messages a closed connection was waiting for, so that one + * Cube Store never answers costs a query rather than a permanently open + * socket. + */ + private abandonClose(socket: CubeStoreWebSocket) { + const unanswered = Object.keys(socket.sentMessages); + + socket.teardown(); + + if (socket === this.webSocket) { + this.webSocket = null; } + + const error = new ConnectionError( + `Cube Store connection was closed with ${unanswered.length} message(s) still unanswered` + ); + + // eslint-disable-next-line no-restricted-syntax + for (const key of unanswered) { + const message = socket.sentMessages[key]; + delete socket.sentMessages[key]; + message.reject(error); + } + + socket.terminate(); + } + + /** + * Closes `socket` once nothing is in flight on it. A no-op until close() has + * been called, so an ordinary idle connection is left alone. + * + * Takes the socket rather than reading `this.webSocket`: a late answer can + * arrive on a socket the re-send path has already superseded, and that must + * not be read as the current connection having drained. + */ + private closeIfDrained(socket: CubeStoreWebSocket | null) { + if (!this.closed || !socket || socket !== this.webSocket) { + return; + } + + if (Object.keys(socket.sentMessages).length) { + return; + } + + // Stop the heartbeat before closing rather than leaving it to the 'close' + // handler: it is the timer, not the socket, that keeps this whole graph + // reachable, so it should not outlive the decision to close by even a tick. + socket.teardown(); + this.webSocket = null; + socket.close(); } } diff --git a/packages/cubejs-cubestore-driver/test/websocket-connection.test.ts b/packages/cubejs-cubestore-driver/test/websocket-connection.test.ts index 33c51c6867249..ddff629837074 100644 --- a/packages/cubejs-cubestore-driver/test/websocket-connection.test.ts +++ b/packages/cubejs-cubestore-driver/test/websocket-connection.test.ts @@ -1,7 +1,8 @@ import { Socket } from 'net'; +import WebSocket from 'ws'; import { WebSocketConnection } from '../src/WebSocketConnection'; -import { MessageTooLargeError, QueryError } from '../src/errors'; +import { ConnectionError, MessageTooLargeError, QueryError } from '../src/errors'; import { QueryResultFormat } from '../codegen'; import { answeredBy, @@ -48,6 +49,22 @@ const waitForBufferedWrite = async (socket: Socket) => { } }; +/** + * Polls until `condition` holds, for assertions about a socket that is torn down + * a few ticks after the call that asked for it. + */ +const waitUntil = async (condition: () => boolean, description: string) => { + const deadline = Date.now() + 5000; + + while (!condition()) { + if (Date.now() > deadline) { + throw new Error(`Timed out waiting for ${description}`); + } + + await new Promise((resolve) => { setTimeout(resolve, 25); }); + } +}; + describe('WebSocketConnection', () => { let server: MockCubeStoreServer; let connection: WebSocketConnection | null = null; @@ -168,6 +185,154 @@ describe('WebSocketConnection', () => { await expectAnsweredBy(query('SELECT 1'), 1); }, JEST_TIMEOUT); + describe('close', () => { + /** Whether Cube Store has seen the connection go away. */ + const closedOnTheServer = (index: number) => server.connection(index).ws.readyState === WebSocket.CLOSED; + + it('closes the socket when nothing is in flight', async () => { + connection = new WebSocketConnection(server.url); + + await expectAnsweredBy(query('SELECT 1'), 0); + + connection.close(); + + await waitUntil(() => closedOnTheServer(0), 'Cube Store to see the connection close'); + expect((connection as any).webSocket).toBeNull(); + }, JEST_TIMEOUT); + + it('keeps the connection open until an in-flight query is answered', async () => { + connection = new WebSocketConnection(server.url); + + let answer: () => void = () => undefined; + server.handler = (message, mockConnection) => { + answer = () => mockConnection.ws.send( + buildErrorMessage(message.messageId, answeredBy(mockConnection.index)) + ); + }; + + const promise = query('SELECT 1'); + await server.waitForMessages(1); + + // An eviction can land mid-query, and Cube Store is already working on the + // answer, so closing must not drop it: the socket stays OPEN rather than + // going into CLOSING out from under the query. + connection.close(); + expect((connection as any).webSocket.readyState).toBe(WebSocket.OPEN); + + answer(); + await expectAnsweredBy(promise, 0); + + await waitUntil(() => closedOnTheServer(0), 'the connection to close once drained'); + expect(server.connections.length).toBe(1); + }, JEST_TIMEOUT); + + it('does not establish a new connection for a query issued after close', async () => { + connection = new WebSocketConnection(server.url); + + await expectAnsweredBy(query('SELECT 1'), 0); + connection.close(); + + // The released driver is still reachable from whoever held it, and this is + // the call that used to re-open the socket the release had just closed. + await expect(query('SELECT 2')).rejects.toThrow(ConnectionError); + expect(server.connections.length).toBe(1); + }, JEST_TIMEOUT); + + it('gives up on a message Cube Store never answers instead of holding the socket', async () => { + // The bound the close waits out. Cube Store keeps answering the pings of a + // connection whose query never completes, so without it nothing would ever + // close this socket again. + process.env.CUBEJS_CUBESTORE_NO_HEART_BEAT_TIMEOUT = '1'; + + try { + connection = new WebSocketConnection(server.url); + + server.handler = () => undefined; + + const promise = query('SELECT 1'); + await server.waitForMessages(1); + + connection.close(); + + await expect(promise).rejects.toThrow(ConnectionError); + await expect(promise).rejects.toThrow('1 message(s) still unanswered'); + + await waitUntil(() => closedOnTheServer(0), 'the connection to be closed anyway'); + expect((connection as any).webSocket).toBeNull(); + } finally { + delete process.env.CUBEJS_CUBESTORE_NO_HEART_BEAT_TIMEOUT; + } + }, JEST_TIMEOUT); + + it('rejects a query that is still connecting when the connection is closed', async () => { + connection = new WebSocketConnection(server.url); + + // `initWebSocket()` creates the socket synchronously, so this close lands + // while it is still CONNECTING -- an eviction while the very first query + // of an orchestrator is connecting. `ws` reports closing a CONNECTING + // socket as an 'error', and nothing else left would settle the promise + // the query is awaiting. + const promise = query('SELECT 1'); + connection.close(); + + await expect(promise).rejects.toThrow(ConnectionError); + await expect(promise).rejects.toThrow('Cube Store connection is closed'); + }, JEST_TIMEOUT); + + it('does not leave an unhandled rejection when a closed connection errors', async () => { + const unhandled: unknown[] = []; + const onUnhandled = (reason: unknown) => { unhandled.push(reason); }; + process.on('unhandledRejection', onUnhandled); + + try { + connection = new WebSocketConnection(server.url); + + // Establish it, so this socket's `readyPromise` is already settled. + await expectAnsweredBy(query('SELECT 1'), 0); + + const socket = (connection as any).webSocket; + connection.close(); + + // A post-open 'error' on a closed connection. Emitted rather than + // provoked because `ws` reports a dying socket as 'close' here, while + // the errors that do reach this handler in the wild -- a protocol error, + // or a connect retry still pending from before the close -- are not + // reproducible against the mock. The handler's contract is the subject: + // it schedules a retry, `initWebSocket()` refuses once closed, and + // handing that rejection to an already-settled `resolve` leaves nobody + // to observe it, which is fatal under Node's default + // --unhandled-rejections=throw. + socket.emit('error', Object.assign(new Error('read ECONNRESET'), { code: 'ECONNRESET' })); + + // Past the retry the handler schedules. + await new Promise((resolve) => { setTimeout(resolve, 2500); }); + + expect(unhandled).toEqual([]); + } finally { + process.off('unhandledRejection', onUnhandled); + } + }, JEST_TIMEOUT); + + it('does not re-open when the socket dies while draining', async () => { + connection = new WebSocketConnection(server.url); + + // Cube Store never answers, so the query is still in flight at close. + server.handler = () => undefined; + + const promise = query('SELECT 1'); + await server.waitForMessages(1); + + connection.close(); + clientSocket().destroy(epipe()); + + // The re-send path would ordinarily raise a fresh connection here, whose + // heartbeat is exactly what kept an evicted orchestrator alive. Failing + // the query is the honest outcome once the connection is closed. + await expect(promise).rejects.toThrow(ConnectionError); + expect(server.connections.length).toBe(1); + }, JEST_TIMEOUT); + }); + describe('message size limit', () => { const MAX_MESSAGE_SIZE = 1024 * 1024; diff --git a/packages/cubejs-databricks-jdbc-driver/CHANGELOG.md b/packages/cubejs-databricks-jdbc-driver/CHANGELOG.md index 49e46739ddcc1..911f91903c4b5 100644 --- a/packages/cubejs-databricks-jdbc-driver/CHANGELOG.md +++ b/packages/cubejs-databricks-jdbc-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/databricks-jdbc-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/databricks-jdbc-driver diff --git a/packages/cubejs-databricks-jdbc-driver/package.json b/packages/cubejs-databricks-jdbc-driver/package.json index 2922707950ae2..6931578c0871e 100644 --- a/packages/cubejs-databricks-jdbc-driver/package.json +++ b/packages/cubejs-databricks-jdbc-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/databricks-jdbc-driver", "description": "Cube.js Databricks database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "license": "Apache-2.0", "repository": { "type": "git", @@ -30,17 +30,17 @@ "bin" ], "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/jdbc-driver": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/jdbc-driver": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "node-fetch": "^2.6.1", "ramda": "^0.27.2", "source-map-support": "^0.5.19", "uuid": "^11.1.1" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "@types/node": "^22", "@types/ramda": "^0.27.34", diff --git a/packages/cubejs-dbt-schema-extension/CHANGELOG.md b/packages/cubejs-dbt-schema-extension/CHANGELOG.md index 6498bcdaf56e4..21f26c87a1bc5 100644 --- a/packages/cubejs-dbt-schema-extension/CHANGELOG.md +++ b/packages/cubejs-dbt-schema-extension/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/dbt-schema-extension + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/dbt-schema-extension diff --git a/packages/cubejs-dbt-schema-extension/package.json b/packages/cubejs-dbt-schema-extension/package.json index c7b5570c335d5..040f701ed5ec2 100644 --- a/packages/cubejs-dbt-schema-extension/package.json +++ b/packages/cubejs-dbt-schema-extension/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/dbt-schema-extension", "description": "Cube.js dbt Schema Extension", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -25,14 +25,14 @@ "lint:fix": "eslint --fix src/* --ext .ts,.js" }, "dependencies": { - "@cubejs-backend/schema-compiler": "1.7.28", + "@cubejs-backend/schema-compiler": "1.7.29", "fs-extra": "^9.1.0", "inflection": "^1.12.0", "node-fetch": "^2.6.1" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing": "1.7.29", "@types/jest": "^29", "jest": "^29", "stream-to-array": "^2.3.0", diff --git a/packages/cubejs-docker/CHANGELOG.md b/packages/cubejs-docker/CHANGELOG.md index dd686784a926c..f0c1c8b58ad1a 100644 --- a/packages/cubejs-docker/CHANGELOG.md +++ b/packages/cubejs-docker/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/docker + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/docker diff --git a/packages/cubejs-docker/package.json b/packages/cubejs-docker/package.json index f91df7753bfb6..9c2fa30da73d7 100644 --- a/packages/cubejs-docker/package.json +++ b/packages/cubejs-docker/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/docker", - "version": "1.7.28", + "version": "1.7.29", "description": "Cube.js In Docker (virtual package)", "author": "Cube Dev, Inc.", "license": "Apache-2.0", @@ -9,34 +9,34 @@ "node": ">=20.0.0" }, "dependencies": { - "@cubejs-backend/athena-driver": "1.7.28", - "@cubejs-backend/bigquery-driver": "1.7.28", - "@cubejs-backend/clickhouse-driver": "1.7.28", - "@cubejs-backend/crate-driver": "1.7.28", - "@cubejs-backend/databricks-jdbc-driver": "1.7.28", - "@cubejs-backend/dbt-schema-extension": "1.7.28", - "@cubejs-backend/dremio-driver": "1.7.28", - "@cubejs-backend/druid-driver": "1.7.28", - "@cubejs-backend/duckdb-driver": "1.7.28", - "@cubejs-backend/firebolt-driver": "1.7.28", - "@cubejs-backend/hive-driver": "1.7.28", - "@cubejs-backend/ksql-driver": "1.7.28", - "@cubejs-backend/materialize-driver": "1.7.28", - "@cubejs-backend/mongobi-driver": "1.7.28", - "@cubejs-backend/mssql-driver": "1.7.28", - "@cubejs-backend/mysql-driver": "1.7.28", - "@cubejs-backend/oracle-driver": "1.7.28", - "@cubejs-backend/pinot-driver": "1.7.28", - "@cubejs-backend/postgres-driver": "1.7.28", - "@cubejs-backend/prestodb-driver": "1.7.28", - "@cubejs-backend/questdb-driver": "1.7.28", - "@cubejs-backend/redshift-driver": "1.7.28", - "@cubejs-backend/server": "1.7.28", - "@cubejs-backend/snowflake-driver": "1.7.28", - "@cubejs-backend/sqlite-driver": "1.7.28", - "@cubejs-backend/trino-driver": "1.7.28", - "@cubejs-backend/vertica-driver": "1.7.28", - "cubejs-cli": "1.7.28", + "@cubejs-backend/athena-driver": "1.7.29", + "@cubejs-backend/bigquery-driver": "1.7.29", + "@cubejs-backend/clickhouse-driver": "1.7.29", + "@cubejs-backend/crate-driver": "1.7.29", + "@cubejs-backend/databricks-jdbc-driver": "1.7.29", + "@cubejs-backend/dbt-schema-extension": "1.7.29", + "@cubejs-backend/dremio-driver": "1.7.29", + "@cubejs-backend/druid-driver": "1.7.29", + "@cubejs-backend/duckdb-driver": "1.7.29", + "@cubejs-backend/firebolt-driver": "1.7.29", + "@cubejs-backend/hive-driver": "1.7.29", + "@cubejs-backend/ksql-driver": "1.7.29", + "@cubejs-backend/materialize-driver": "1.7.29", + "@cubejs-backend/mongobi-driver": "1.7.29", + "@cubejs-backend/mssql-driver": "1.7.29", + "@cubejs-backend/mysql-driver": "1.7.29", + "@cubejs-backend/oracle-driver": "1.7.29", + "@cubejs-backend/pinot-driver": "1.7.29", + "@cubejs-backend/postgres-driver": "1.7.29", + "@cubejs-backend/prestodb-driver": "1.7.29", + "@cubejs-backend/questdb-driver": "1.7.29", + "@cubejs-backend/redshift-driver": "1.7.29", + "@cubejs-backend/server": "1.7.29", + "@cubejs-backend/snowflake-driver": "1.7.29", + "@cubejs-backend/sqlite-driver": "1.7.29", + "@cubejs-backend/trino-driver": "1.7.29", + "@cubejs-backend/vertica-driver": "1.7.29", + "cubejs-cli": "1.7.29", "typescript": "~5.2.2" }, "resolutions": { diff --git a/packages/cubejs-dremio-driver/CHANGELOG.md b/packages/cubejs-dremio-driver/CHANGELOG.md index a059d13f61e03..4c2d50f3a98e8 100644 --- a/packages/cubejs-dremio-driver/CHANGELOG.md +++ b/packages/cubejs-dremio-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/dremio-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/dremio-driver diff --git a/packages/cubejs-dremio-driver/package.json b/packages/cubejs-dremio-driver/package.json index ee512d1cf0323..6b29078c87ff8 100644 --- a/packages/cubejs-dremio-driver/package.json +++ b/packages/cubejs-dremio-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/dremio-driver", "description": "Cube.js Dremio driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -23,14 +23,14 @@ "lint:fix": "eslint driver/*.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "axios": "^1.8.3" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "jest": "^29" }, "license": "Apache-2.0", diff --git a/packages/cubejs-druid-driver/CHANGELOG.md b/packages/cubejs-druid-driver/CHANGELOG.md index 6801d2383e144..67c75300f4805 100644 --- a/packages/cubejs-druid-driver/CHANGELOG.md +++ b/packages/cubejs-druid-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/druid-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) ### Features diff --git a/packages/cubejs-druid-driver/package.json b/packages/cubejs-druid-driver/package.json index 9117e25e3847a..d03a18a18b7e6 100644 --- a/packages/cubejs-druid-driver/package.json +++ b/packages/cubejs-druid-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/druid-driver", "description": "Cube.js Druid database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "license": "Apache-2.0", "repository": { "type": "git", @@ -28,13 +28,13 @@ "dist/src/*" ], "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "axios": "^1.8.3" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "@types/node": "^22", "jest": "^29", diff --git a/packages/cubejs-duckdb-driver/CHANGELOG.md b/packages/cubejs-duckdb-driver/CHANGELOG.md index 21e7f6bcb754a..1546c9bb0980c 100644 --- a/packages/cubejs-duckdb-driver/CHANGELOG.md +++ b/packages/cubejs-duckdb-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/duckdb-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/duckdb-driver diff --git a/packages/cubejs-duckdb-driver/package.json b/packages/cubejs-duckdb-driver/package.json index 023c6fca31241..1c71360f9b773 100644 --- a/packages/cubejs-duckdb-driver/package.json +++ b/packages/cubejs-duckdb-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/duckdb-driver", "description": "Cube DuckDB database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,15 +28,15 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "duckdb": "^1.4.1" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "@types/jest": "^29", "@types/node": "^22", "jest": "^29", diff --git a/packages/cubejs-firebolt-driver/CHANGELOG.md b/packages/cubejs-firebolt-driver/CHANGELOG.md index 6d5e5bcca144a..f5ca7b1f210ac 100644 --- a/packages/cubejs-firebolt-driver/CHANGELOG.md +++ b/packages/cubejs-firebolt-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/firebolt-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/firebolt-driver diff --git a/packages/cubejs-firebolt-driver/package.json b/packages/cubejs-firebolt-driver/package.json index 3edb279d6ce72..9b6d95577747c 100644 --- a/packages/cubejs-firebolt-driver/package.json +++ b/packages/cubejs-firebolt-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/firebolt-driver", "description": "Cube.js Firebolt database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,15 +28,15 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "firebolt-sdk": "1.10.0" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "typescript": "~5.2.2" }, "publishConfig": { diff --git a/packages/cubejs-hive-driver/CHANGELOG.md b/packages/cubejs-hive-driver/CHANGELOG.md index 113b054d3599c..ebb2eb808632b 100644 --- a/packages/cubejs-hive-driver/CHANGELOG.md +++ b/packages/cubejs-hive-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/hive-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/hive-driver diff --git a/packages/cubejs-hive-driver/package.json b/packages/cubejs-hive-driver/package.json index fd22801d8d870..bd8526a025c80 100644 --- a/packages/cubejs-hive-driver/package.json +++ b/packages/cubejs-hive-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/hive-driver", "description": "Cube.js Hive database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -17,8 +17,8 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "jshs2": "^0.4.4", "sasl-plain": "^0.1.0", "saslmechanisms": "^0.1.1", @@ -27,7 +27,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28" + "@cubejs-backend/linter": "1.7.29" }, "publishConfig": { "access": "public" diff --git a/packages/cubejs-jdbc-driver/CHANGELOG.md b/packages/cubejs-jdbc-driver/CHANGELOG.md index 2ac4c9f5ff3dd..96e8537b2b83e 100644 --- a/packages/cubejs-jdbc-driver/CHANGELOG.md +++ b/packages/cubejs-jdbc-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/jdbc-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/jdbc-driver diff --git a/packages/cubejs-jdbc-driver/package.json b/packages/cubejs-jdbc-driver/package.json index 1e65bbe900350..50e99318b7e66 100644 --- a/packages/cubejs-jdbc-driver/package.json +++ b/packages/cubejs-jdbc-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/jdbc-driver", "description": "Cube.js JDBC database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -26,9 +26,9 @@ "index.js" ], "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", "@cubejs-backend/node-java-maven": "^0.1.3", - "@cubejs-backend/shared": "1.7.28" + "@cubejs-backend/shared": "1.7.29" }, "optionalDependencies": { "@cubejs-backend/jdbc": "^0.9.0", @@ -42,7 +42,7 @@ "testEnvironment": "node" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/node": "^22", "typescript": "~5.2.2" } diff --git a/packages/cubejs-ksql-driver/CHANGELOG.md b/packages/cubejs-ksql-driver/CHANGELOG.md index eb0a5314cd698..d33341218ca76 100644 --- a/packages/cubejs-ksql-driver/CHANGELOG.md +++ b/packages/cubejs-ksql-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/ksql-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) ### Features diff --git a/packages/cubejs-ksql-driver/package.json b/packages/cubejs-ksql-driver/package.json index 9808f03761b60..cd6bce6c1b8f9 100644 --- a/packages/cubejs-ksql-driver/package.json +++ b/packages/cubejs-ksql-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/ksql-driver", "description": "Cube.js ksql database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -26,9 +26,9 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "async-mutex": "0.3.2", "axios": "^1.8.3", "kafkajs": "^2.2.3" @@ -41,7 +41,7 @@ "extends": "../cubejs-linter" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "typescript": "~5.2.2" } } diff --git a/packages/cubejs-linter/CHANGELOG.md b/packages/cubejs-linter/CHANGELOG.md index 49d676961bfd7..c0f0eca4c0094 100644 --- a/packages/cubejs-linter/CHANGELOG.md +++ b/packages/cubejs-linter/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/linter + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/linter diff --git a/packages/cubejs-linter/package.json b/packages/cubejs-linter/package.json index 6325b24d02c89..d5547783f4c36 100644 --- a/packages/cubejs-linter/package.json +++ b/packages/cubejs-linter/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/linter", "description": "Cube.js ESLint (virtual package) for linting code", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", diff --git a/packages/cubejs-materialize-driver/CHANGELOG.md b/packages/cubejs-materialize-driver/CHANGELOG.md index 09dfa15906788..4859b15f48624 100644 --- a/packages/cubejs-materialize-driver/CHANGELOG.md +++ b/packages/cubejs-materialize-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/materialize-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/materialize-driver diff --git a/packages/cubejs-materialize-driver/package.json b/packages/cubejs-materialize-driver/package.json index a35bfd95fd763..46ebc306c9cf5 100644 --- a/packages/cubejs-materialize-driver/package.json +++ b/packages/cubejs-materialize-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/materialize-driver", "description": "Cube.js Materialize database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,15 +27,15 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/postgres-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/postgres-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "semver": "^7.6.3" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing": "1.7.29", "typescript": "~5.2.2" }, "publishConfig": { diff --git a/packages/cubejs-mongobi-driver/CHANGELOG.md b/packages/cubejs-mongobi-driver/CHANGELOG.md index 36ea9dbef5570..65012a88cf6bb 100644 --- a/packages/cubejs-mongobi-driver/CHANGELOG.md +++ b/packages/cubejs-mongobi-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/mongobi-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/mongobi-driver diff --git a/packages/cubejs-mongobi-driver/package.json b/packages/cubejs-mongobi-driver/package.json index deec376e37ebd..099409fc5e8c4 100644 --- a/packages/cubejs-mongobi-driver/package.json +++ b/packages/cubejs-mongobi-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/mongobi-driver", "description": "Cube.js MongoBI driver", "author": "krunalsabnis@gmail.com", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,8 +27,8 @@ "integration:mongobi": "jest dist/test" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "@types/node": "^22", "moment": "^2.29.1", "mysql2": "^3.11.5" @@ -38,7 +38,7 @@ "access": "public" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "testcontainers": "^10.28.0", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-mssql-driver/CHANGELOG.md b/packages/cubejs-mssql-driver/CHANGELOG.md index d13c7056c13a2..0f9cdfc6ed851 100644 --- a/packages/cubejs-mssql-driver/CHANGELOG.md +++ b/packages/cubejs-mssql-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/mssql-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/mssql-driver diff --git a/packages/cubejs-mssql-driver/package.json b/packages/cubejs-mssql-driver/package.json index ac18b8d7c6a5e..6d171a5f101b5 100644 --- a/packages/cubejs-mssql-driver/package.json +++ b/packages/cubejs-mssql-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/mssql-driver", "description": "Cube.js MS SQL database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -25,8 +25,8 @@ "lint:fix": "eslint --fix src/* --ext .ts,.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "mssql": "^11.0.1" }, "devDependencies": { diff --git a/packages/cubejs-mysql-aurora-serverless-driver/CHANGELOG.md b/packages/cubejs-mysql-aurora-serverless-driver/CHANGELOG.md index bac945f8ce0b3..500c5fba057e9 100644 --- a/packages/cubejs-mysql-aurora-serverless-driver/CHANGELOG.md +++ b/packages/cubejs-mysql-aurora-serverless-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/mysql-aurora-serverless-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/mysql-aurora-serverless-driver diff --git a/packages/cubejs-mysql-aurora-serverless-driver/package.json b/packages/cubejs-mysql-aurora-serverless-driver/package.json index ad32f02783745..39902c25528dd 100644 --- a/packages/cubejs-mysql-aurora-serverless-driver/package.json +++ b/packages/cubejs-mysql-aurora-serverless-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/mysql-aurora-serverless-driver", "description": "Cube.js Aurora Serverless Mysql database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -21,14 +21,14 @@ "lint": "eslint driver/*.js test/*.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "@types/mysql": "^2.15.15", "aws-sdk": "^2.787.0", "data-api-client": "^1.1.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/data-api-client": "^1.2.1", "@types/jest": "^29", "jest": "^29", diff --git a/packages/cubejs-mysql-driver/CHANGELOG.md b/packages/cubejs-mysql-driver/CHANGELOG.md index 07b42b5b95027..6e4d408bdef6b 100644 --- a/packages/cubejs-mysql-driver/CHANGELOG.md +++ b/packages/cubejs-mysql-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/mysql-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/mysql-driver diff --git a/packages/cubejs-mysql-driver/package.json b/packages/cubejs-mysql-driver/package.json index 6c0712b1710c4..43380cb9c2fe2 100644 --- a/packages/cubejs-mysql-driver/package.json +++ b/packages/cubejs-mysql-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/mysql-driver", "description": "Cube.js Mysql database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,13 +27,13 @@ "lint:fix": "eslint --fix src/* test/* --ext .ts,.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "mysql2": "^3.16.1" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "@types/jest": "^29", "jest": "^29", "stream-to-array": "^2.3.0", diff --git a/packages/cubejs-oracle-driver/CHANGELOG.md b/packages/cubejs-oracle-driver/CHANGELOG.md index 1e600ebc94a1b..e501d633f729e 100644 --- a/packages/cubejs-oracle-driver/CHANGELOG.md +++ b/packages/cubejs-oracle-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/oracle-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/oracle-driver diff --git a/packages/cubejs-oracle-driver/package.json b/packages/cubejs-oracle-driver/package.json index 680ac47c6de65..810a0419f388c 100644 --- a/packages/cubejs-oracle-driver/package.json +++ b/packages/cubejs-oracle-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/oracle-driver", "description": "Cube.js oracle database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -13,8 +13,8 @@ }, "main": "driver/OracleDriver.js", "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "ramda": "^0.27.0" }, "devDependencies": { diff --git a/packages/cubejs-pinot-driver/CHANGELOG.md b/packages/cubejs-pinot-driver/CHANGELOG.md index fdc893e539d8d..69cc30ee8382c 100644 --- a/packages/cubejs-pinot-driver/CHANGELOG.md +++ b/packages/cubejs-pinot-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/pinot-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/pinot-driver diff --git a/packages/cubejs-pinot-driver/package.json b/packages/cubejs-pinot-driver/package.json index 98e8deaa4b33d..ee8796faf305b 100644 --- a/packages/cubejs-pinot-driver/package.json +++ b/packages/cubejs-pinot-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/pinot-driver", "description": "Cube.js Pinot database driver", "author": "Julian Ronsse, InTheMemory, Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,9 +28,9 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "node-fetch": "^2.6.1", "ramda": "^0.27.2" }, @@ -39,7 +39,7 @@ "access": "public" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "jest": "^29", "should": "^13.2.3", diff --git a/packages/cubejs-playground/CHANGELOG.md b/packages/cubejs-playground/CHANGELOG.md index 7116424ca53f6..af1d2e9337020 100644 --- a/packages/cubejs-playground/CHANGELOG.md +++ b/packages/cubejs-playground/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-client/playground + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-client/playground diff --git a/packages/cubejs-playground/package.json b/packages/cubejs-playground/package.json index 9c87f3853a47b..0c2cdbd104cc0 100644 --- a/packages/cubejs-playground/package.json +++ b/packages/cubejs-playground/package.json @@ -1,7 +1,7 @@ { "name": "@cubejs-client/playground", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "engines": {}, "repository": { "type": "git", @@ -68,8 +68,8 @@ "@ant-design/compatible": "^1.0.1", "@ant-design/icons": "^5.3.5", "@cube-dev/ui-kit": "0.52.3", - "@cubejs-client/core": "1.7.28", - "@cubejs-client/react": "1.7.28", + "@cubejs-client/core": "1.7.29", + "@cubejs-client/react": "1.7.29", "@types/flexsearch": "^0.7.3", "@types/node": "^22", "@types/react": "^18.3.4", diff --git a/packages/cubejs-postgres-driver/CHANGELOG.md b/packages/cubejs-postgres-driver/CHANGELOG.md index 196a07cb318ce..c26014a063d87 100644 --- a/packages/cubejs-postgres-driver/CHANGELOG.md +++ b/packages/cubejs-postgres-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/postgres-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/postgres-driver diff --git a/packages/cubejs-postgres-driver/package.json b/packages/cubejs-postgres-driver/package.json index 57d341c6b6f21..f9e595998d8c4 100644 --- a/packages/cubejs-postgres-driver/package.json +++ b/packages/cubejs-postgres-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/postgres-driver", "description": "Cube.js Postgres database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,8 +27,8 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "@types/pg": "^8.16.0", "@types/pg-query-stream": "^1.0.3", "pg": "^8.18.0", @@ -36,8 +36,8 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "testcontainers": "^10.28.0", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-prestodb-driver/CHANGELOG.md b/packages/cubejs-prestodb-driver/CHANGELOG.md index 3e288fad3ad90..e97f38e12cf7f 100644 --- a/packages/cubejs-prestodb-driver/CHANGELOG.md +++ b/packages/cubejs-prestodb-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/prestodb-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/prestodb-driver diff --git a/packages/cubejs-prestodb-driver/package.json b/packages/cubejs-prestodb-driver/package.json index e59b168c37d20..838bac240b0c6 100644 --- a/packages/cubejs-prestodb-driver/package.json +++ b/packages/cubejs-prestodb-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/prestodb-driver", "description": "Cube.js Presto database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,8 +28,8 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "presto-client": "1.2.0", "ramda": "^0.27.0" }, @@ -38,7 +38,7 @@ "access": "public" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "jest": "^29", "should": "^13.2.3", diff --git a/packages/cubejs-query-orchestrator/CHANGELOG.md b/packages/cubejs-query-orchestrator/CHANGELOG.md index d1a8c51c62e2a..08906729b2ace 100644 --- a/packages/cubejs-query-orchestrator/CHANGELOG.md +++ b/packages/cubejs-query-orchestrator/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +### Bug Fixes + +- **api-gateway:** Make pre-agg build job status per-entry and data-source aware ([#11666](https://github.com/cube-js/cube/issues/11666)) ([694955f](https://github.com/cube-js/cube/commit/694955f864375515d84d463e9e7ee89a156a4a2b)) +- **query-orchestrator:** initialize missing data source queue in isPartitionExist, thanks [@dochernyshov](https://github.com/dochernyshov) ([#11617](https://github.com/cube-js/cube/issues/11617)) ([ab48039](https://github.com/cube-js/cube/commit/ab48039f1a3d46d890367b0fc898d9ebf1322786)) + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/query-orchestrator diff --git a/packages/cubejs-query-orchestrator/package.json b/packages/cubejs-query-orchestrator/package.json index 9d1c663d82f18..de328f49fa74b 100644 --- a/packages/cubejs-query-orchestrator/package.json +++ b/packages/cubejs-query-orchestrator/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/query-orchestrator", "description": "Cube.js Query Orchestrator and Cache", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -30,15 +30,15 @@ "dist/src/*" ], "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/cubestore-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/cubestore-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "csv-write-stream": "^2.0.0", "lru-cache": "^11.1.0", "ramda": "^0.27.2" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "@types/node": "^22", "@types/ramda": "^0.27.32", diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/QueryCache.ts b/packages/cubejs-query-orchestrator/src/orchestrator/QueryCache.ts index 70d2c35f86958..c500df4681d8f 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/QueryCache.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/QueryCache.ts @@ -121,13 +121,30 @@ export type CacheKey = [CacheKeyItem, CacheKeyItem, CacheKeyItem] | [CacheKeyItem, CacheKeyItem, CacheKeyItem, CacheKeyItem]; -type CacheEntry = { +export type CacheEntry = { time: number; result: any; - renewalKey: string; + renewalKey?: string; requestId?: string; }; +export enum CacheAction { + ServeCached = 'serve-cached', + RefreshSameRequest = 'refresh-same-request', + RefreshBackground = 'refresh-background', + WaitForRenew = 'wait-for-renew', +} + +type CacheOperationContext = { + cacheKey: CacheKey; + redisKey: string; + renewalKey?: string; + expiration: number; + spanId: string; + options: CacheQueryResultOptions; + log: (message: string, extra?: Record) => void; +}; + export interface QueryCacheOptions { refreshKeyRenewalThreshold?: number; externalQueueOptions?: any; @@ -156,6 +173,8 @@ export class QueryCache { protected memoryCache: LRUCache; + protected static readonly IN_MEMORY_CACHE_DISABLE_PERIOD = 5 * 60 * 1000; + public constructor( protected readonly cachePrefix: string, protected readonly driverFactory: DriverFactoryByDataSource, @@ -888,174 +907,244 @@ export class QueryCache { callback: () => MaybeCancelablePromise, ) => this.cacheDriver.withLock(`lock:${key}`, callback, ttl, true); - public async cacheQueryResult( - query: string | QueryWithParams, - values: string[], + protected static decideCacheAction( + entry: CacheEntry, + renewedAgo: number, + options: CacheQueryResultOptions, + renewalKey?: string, + ): CacheAction { + const { renewalThreshold } = options; + const isExpired = !renewalThreshold || !entry.time || renewedAgo > renewalThreshold * 1000; + const isKeyMismatch = !!renewalKey && entry.renewalKey !== renewalKey; + + if (!isExpired && !isKeyMismatch) { + return CacheAction.ServeCached; + } + + const isSameRequest = options.requestId && entry.requestId && + QueryCache.extractRequestUUID(entry.requestId) === QueryCache.extractRequestUUID(options.requestId); + + // A client polling through continue-wait re-enters with the same requestId, so rejecting + // the result it just wrote would restart the fetch on every poll and never converge while + // the refreshKey keeps moving. Background renew opts out: fresh data is all it exists for. + if (isSameRequest && !options.renewCycle) { + return CacheAction.RefreshSameRequest; + } + + // Without a refreshKey there is nothing to refresh against, so an elapsed threshold alone never triggers a fetch. + if (!renewalKey) { + return CacheAction.ServeCached; + } + + return options.waitForRenew ? CacheAction.WaitForRenew : CacheAction.RefreshBackground; + } + + protected static isMemoryEntryUsable( + entry: CacheEntry, + renewedAgo: number, + expiration: number, + renewalThreshold?: number, + renewalKey?: string, + ): boolean { + if (renewedAgo > expiration * 1000 || renewedAgo > QueryCache.IN_MEMORY_CACHE_DISABLE_PERIOD) { + return false; + } + + if (!renewalKey) { + return true; + } + + // Near expiry an in-memory entry races with refreshes carrying a different refreshKey value. + return !!renewalThreshold && + !!entry.time && + renewedAgo + QueryCache.IN_MEMORY_CACHE_DISABLE_PERIOD <= renewalThreshold * 1000 && + entry.renewalKey === renewalKey; + } + + protected cacheOperationContext( cacheKey: CacheKey, expiration: number, options: CacheQueryResultOptions, - ) { + ): CacheOperationContext { const spanId = crypto.randomBytes(16).toString('hex'); - options = options || { dataSource: 'default' }; - const { renewalThreshold, primaryQuery, renewCycle } = options; - const renewalKey = options.renewalKey && this.queryRedisKey(options.renewalKey); - const redisKey = this.queryRedisKey(cacheKey); - const fetchNew = () => ( - this.queryWithRetryAndRelease(query, values, { - cacheKey, - priority: options.priority, - external: options.external, + const logContext = { + cacheKey, + requestId: options.requestId, + spanId, + primaryQuery: options.primaryQuery, + renewCycle: options.renewCycle, + }; + + return { + cacheKey, + redisKey: this.queryRedisKey(cacheKey), + renewalKey: options.renewalKey && this.queryRedisKey(options.renewalKey), + expiration, + spanId, + options, + log: (message, extra) => this.logger(message, extra ? { ...logContext, ...extra } : logContext), + }; + } + + protected fetchAndCacheQuery( + query: string | QueryWithParams, + values: string[], + ctx: CacheOperationContext, + ) { + const { cacheKey, redisKey, renewalKey, expiration, spanId, options } = ctx; + + return this.queryWithRetryAndRelease(query, values, { + cacheKey, + priority: options.priority, + external: options.external, + requestId: options.requestId, + spanId, + persistent: options.persistent, + dataSource: options.dataSource, + useCsvQuery: options.useCsvQuery, + lambdaTypes: options.lambdaTypes, + }).then(res => { + const entry = { + time: (new Date()).getTime(), + result: res, + renewalKey, requestId: options.requestId, - spanId, - persistent: options.persistent, - dataSource: options.dataSource, - useCsvQuery: options.useCsvQuery, - lambdaTypes: options.lambdaTypes, - }).then(res => { - const result = { - time: (new Date()).getTime(), - result: res, - renewalKey, - requestId: options.requestId, - }; - return this - .cacheDriver - .set(redisKey, result, expiration) - .then(({ bytes }) => { - this.logger('Renewed', { cacheKey, requestId: options.requestId, spanId, primaryQuery, renewCycle }); - this.logger('Outgoing network usage', { - service: 'cache', - requestId: options.requestId, - spanId, - bytes, - cacheKey, - }); - return res; - }); - }).catch(e => { - if (!(e instanceof ContinueWaitError)) { - this.logger('Dropping Cache', { - cacheKey, - error: e.stack || e, + }; + + return this + .cacheDriver + .set(redisKey, entry, expiration) + .then(({ bytes }) => { + ctx.log('Renewed'); + this.logger('Outgoing network usage', { + service: 'cache', requestId: options.requestId, spanId, - primaryQuery, - renewCycle + bytes, + cacheKey, }); - this.cacheDriver.remove(redisKey) - .catch(err => this.logger('Error removing key', { - cacheKey, - spanId, - error: err.stack || err, - requestId: options.requestId - })); - } - throw e; - }) - ); + return res; + }); + }).catch(e => { + if (!(e instanceof ContinueWaitError)) { + ctx.log('Dropping Cache', { error: e.stack || e }); + this.cacheDriver.remove(redisKey) + .catch(err => this.logger('Error removing key', { + cacheKey, + spanId, + error: err.stack || err, + requestId: options.requestId + })); + } + throw e; + }); + } + + protected fetchAndCacheQueryInBackground( + query: string | QueryWithParams, + values: string[], + ctx: CacheOperationContext, + ): void { + this.fetchAndCacheQuery(query, values, ctx).catch(e => { + if (!(e instanceof ContinueWaitError)) { + ctx.log('Error renewing', { error: e.stack || e }); + } + }); + } + + protected getFromMemoryCache(ctx: CacheOperationContext): CacheEntry | null { + const { redisKey, renewalKey, expiration, options } = ctx; + const entry = this.memoryCache.get(redisKey); + + if (!entry) { + return null; + } + + const renewedAgo = (new Date()).getTime() - entry.time; + + if (!QueryCache.isMemoryEntryUsable(entry, renewedAgo, expiration, options.renewalThreshold, renewalKey)) { + this.memoryCache.delete(redisKey); + return null; + } + + ctx.log('Found in memory cache entry', { + time: entry.time, + renewedAgo, + renewalKey: entry.renewalKey, + newRenewalKey: renewalKey, + renewalThreshold: options.renewalThreshold, + }); + + return entry; + } + + protected storeInMemoryCache(entry: CacheEntry, renewedAgo: number, ctx: CacheOperationContext): void { + const { useInMemory, renewalThreshold } = ctx.options; + + if (useInMemory && !!renewalThreshold && + renewedAgo + QueryCache.IN_MEMORY_CACHE_DISABLE_PERIOD <= renewalThreshold * 1000) { + this.memoryCache.set(ctx.redisKey, entry); + } + } + + public async cacheQueryResult( + query: string | QueryWithParams, + values: string[], + cacheKey: CacheKey, + expiration: number, + options: CacheQueryResultOptions, + ) { + options = options || { dataSource: 'default' }; + + const ctx = this.cacheOperationContext(cacheKey, expiration, options); + const { renewalThreshold } = options; if (options.forceNoCache) { - this.logger('Force no cache for', { cacheKey, requestId: options.requestId, spanId, primaryQuery, renewCycle }); - return fetchNew(); + ctx.log('Force no cache for'); + return this.fetchAndCacheQuery(query, values, ctx); } - let res; - - const inMemoryCacheDisablePeriod = 5 * 60 * 1000; - - if (options.useInMemory) { - const inMemoryValue = this.memoryCache.get(redisKey); - if (inMemoryValue) { - const renewedAgo = (new Date()).getTime() - inMemoryValue.time; - - if ( - renewalKey && ( - !renewalThreshold || - !inMemoryValue.time || - // Do not cache in memory in last 5 minutes of expiry. - // Most likely it'll cause race condition of refreshing data with different refreshKey values. - renewedAgo + inMemoryCacheDisablePeriod > renewalThreshold * 1000 || - inMemoryValue.renewalKey !== renewalKey - ) || renewedAgo > expiration * 1000 || renewedAgo > inMemoryCacheDisablePeriod - ) { - this.memoryCache.delete(redisKey); - } else { - this.logger('Found in memory cache entry', { - cacheKey, - time: inMemoryValue.time, - renewedAgo, - renewalKey: inMemoryValue.renewalKey, - newRenewalKey: renewalKey, - renewalThreshold, - requestId: options.requestId, - spanId, - primaryQuery, - renewCycle - }); - res = inMemoryValue; - } - } + let entry: CacheEntry | null = options.useInMemory ? this.getFromMemoryCache(ctx) : null; + + if (!entry) { + entry = await this.cacheDriver.get(ctx.redisKey); } - if (!res) { - res = await this.cacheDriver.get(redisKey); + if (!entry) { + ctx.log('Missing cache for'); + return this.fetchAndCacheQuery(query, values, ctx); } - if (res) { - const parsedResult = res; - const renewedAgo = (new Date()).getTime() - parsedResult.time; - this.logger('Found cache entry', { - cacheKey, - time: parsedResult.time, - renewedAgo, - renewalKey: parsedResult.renewalKey, - newRenewalKey: renewalKey, - renewalThreshold, - requestId: options.requestId, - spanId, - primaryQuery, - renewCycle - }); - - const isExpired = !renewalThreshold || !parsedResult.time || renewedAgo > renewalThreshold * 1000; - const isKeyMismatch = renewalKey && parsedResult.renewalKey !== renewalKey; - const isSameRequest = options.requestId && parsedResult.requestId && - QueryCache.extractRequestUUID(parsedResult.requestId) === QueryCache.extractRequestUUID(options.requestId); - - // Continue-wait cycle: result was produced by our request, - // refreshKey changed during execution — return cached, refresh in background. - // Skip for renewCycle — it must always fetch fresh data to keep cache up-to-date. - if (isSameRequest && !renewCycle && (isExpired || isKeyMismatch)) { - this.logger('Same request cache hit (background refresh)', { cacheKey, renewalThreshold, requestId: options.requestId, spanId, primaryQuery, renewCycle }); - fetchNew().catch(e => { - if (!(e instanceof ContinueWaitError)) { - this.logger('Error renewing', { cacheKey, error: e.stack || e, requestId: options.requestId, spanId, primaryQuery, renewCycle }); - } - }); - } else if (renewalKey && (isExpired || isKeyMismatch)) { - // Cache expired or refreshKey changed — need to refresh - if (options.waitForRenew) { - this.logger('Waiting for renew', { cacheKey, renewalThreshold, requestId: options.requestId, spanId, primaryQuery, renewCycle }); - return fetchNew(); - } else { - this.logger('Renewing existing key', { cacheKey, renewalThreshold, requestId: options.requestId, spanId, primaryQuery, renewCycle }); - fetchNew().catch(e => { - if (!(e instanceof ContinueWaitError)) { - this.logger('Error renewing', { cacheKey, error: e.stack || e, requestId: options.requestId, spanId, primaryQuery, renewCycle }); - } - }); - } - } + const renewedAgo = (new Date()).getTime() - entry.time; - this.logger('Using cache for', { cacheKey, requestId: options.requestId, spanId, primaryQuery, renewCycle }); - if (options.useInMemory && renewedAgo + inMemoryCacheDisablePeriod <= renewalThreshold * 1000) { - this.memoryCache.set(redisKey, parsedResult); - } - return parsedResult.result; - } else { - this.logger('Missing cache for', { cacheKey, requestId: options.requestId, spanId, primaryQuery, renewCycle }); - return fetchNew(); + ctx.log('Found cache entry', { + time: entry.time, + renewedAgo, + renewalKey: entry.renewalKey, + newRenewalKey: ctx.renewalKey, + renewalThreshold, + }); + + switch (QueryCache.decideCacheAction(entry, renewedAgo, options, ctx.renewalKey)) { + case CacheAction.WaitForRenew: + ctx.log('Waiting for renew', { renewalThreshold }); + return this.fetchAndCacheQuery(query, values, ctx); + case CacheAction.RefreshSameRequest: + ctx.log('Same request cache hit (background refresh)', { renewalThreshold }); + this.fetchAndCacheQueryInBackground(query, values, ctx); + break; + case CacheAction.RefreshBackground: + ctx.log('Renewing existing key', { renewalThreshold }); + this.fetchAndCacheQueryInBackground(query, values, ctx); + break; + default: + break; } + + ctx.log('Using cache for'); + this.storeInMemoryCache(entry, renewedAgo, ctx); + + return entry.result; } protected async lastRefreshTime(cacheKey) { diff --git a/packages/cubejs-query-orchestrator/test/unit/QueryCache.test.ts b/packages/cubejs-query-orchestrator/test/unit/QueryCache.test.ts index 6cdbdb684da2d..578ebc3d89de0 100644 --- a/packages/cubejs-query-orchestrator/test/unit/QueryCache.test.ts +++ b/packages/cubejs-query-orchestrator/test/unit/QueryCache.test.ts @@ -1,5 +1,290 @@ +import { CacheAction, CacheEntry, CacheQueryResultOptions, QueryCache } from '../../src'; import { QueryCacheTest } from './QueryCache.abstract'; QueryCacheTest('Local', { cacheAndQueueDriver: 'memory', }); + +class QueryCacheOpened extends QueryCache { + public static get disablePeriod(): number { + return this.IN_MEMORY_CACHE_DISABLE_PERIOD; + } + + public static decideCacheAction( + entry: Partial, + renewedAgo: number, + options: Partial, + renewalKey?: string, + ): CacheAction { + return super.decideCacheAction( + entry as CacheEntry, + renewedAgo, + options as CacheQueryResultOptions, + renewalKey, + ); + } + + public static isMemoryEntryUsable( + entry: Partial, + renewedAgo: number, + expiration: number, + renewalThreshold?: number, + renewalKey?: string, + ): boolean { + return super.isMemoryEntryUsable( + entry as CacheEntry, + renewedAgo, + expiration, + renewalThreshold, + renewalKey, + ); + } +} + +describe('QueryCache.decideCacheAction', () => { + const THRESHOLD = 600; + const NOT_EXPIRED = 100 * 1000; + const EXPIRED = 700 * 1000; + + type Case = { + name: string, + entry: Partial, + renewedAgo: number, + options: Partial, + renewalKey?: string, + expected: CacheAction, + }; + + const cases: Case[] = [ + { + name: 'not expired and renewal key matches: serves cache even for the same request', + entry: { time: 1, renewalKey: 'rk', requestId: 'req-1-span-1' }, + renewedAgo: NOT_EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-1-span-2', waitForRenew: true }, + renewalKey: 'rk', + expected: CacheAction.ServeCached, + }, + { + name: 'expired with renewal key and waitForRenew: blocks on fetch', + entry: { time: 1, renewalKey: 'rk', requestId: 'req-1' }, + renewedAgo: EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-2', waitForRenew: true }, + renewalKey: 'rk', + expected: CacheAction.WaitForRenew, + }, + { + name: 'expired with renewal key without waitForRenew: refreshes in background', + entry: { time: 1, renewalKey: 'rk', requestId: 'req-1' }, + renewedAgo: EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-2', waitForRenew: false }, + renewalKey: 'rk', + expected: CacheAction.RefreshBackground, + }, + { + name: 'renewal key mismatch while not expired and waitForRenew: blocks on fetch', + entry: { time: 1, renewalKey: 'old', requestId: 'req-1' }, + renewedAgo: NOT_EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-2', waitForRenew: true }, + renewalKey: 'new', + expected: CacheAction.WaitForRenew, + }, + { + name: 'renewal key mismatch while not expired without waitForRenew: refreshes in background', + entry: { time: 1, renewalKey: 'old', requestId: 'req-1' }, + renewedAgo: NOT_EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-2', waitForRenew: false }, + renewalKey: 'new', + expected: CacheAction.RefreshBackground, + }, + { + name: 'same request (different span) and expired: serves stale, refreshes in background', + entry: { time: 1, renewalKey: 'rk', requestId: 'req-1-span-1' }, + renewedAgo: EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-1-span-7', waitForRenew: true }, + renewalKey: 'rk', + expected: CacheAction.RefreshSameRequest, + }, + { + name: 'same request and renewal key mismatch: serves stale, refreshes in background', + entry: { time: 1, renewalKey: 'old', requestId: 'req-1-span-1' }, + renewedAgo: NOT_EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-1-span-7', waitForRenew: true }, + renewalKey: 'new', + expected: CacheAction.RefreshSameRequest, + }, + { + name: 'renew cycle never serves stale: expired same request blocks on fetch', + entry: { time: 1, renewalKey: 'rk', requestId: 'req-1-span-1' }, + renewedAgo: EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-1-span-7', waitForRenew: true, renewCycle: true }, + renewalKey: 'rk', + expected: CacheAction.WaitForRenew, + }, + { + name: 'renew cycle never serves stale: key mismatch without waitForRenew refreshes in background', + entry: { time: 1, renewalKey: 'old', requestId: 'req-1-span-1' }, + renewedAgo: NOT_EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-1-span-7', waitForRenew: false, renewCycle: true }, + renewalKey: 'new', + expected: CacheAction.RefreshBackground, + }, + { + name: 'expired without a renewal key: keeps serving cache', + entry: { time: 1, requestId: 'req-1' }, + renewedAgo: EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-2', waitForRenew: true }, + expected: CacheAction.ServeCached, + }, + { + name: 'expired without a renewal key but same request: refreshes in background', + entry: { time: 1, requestId: 'req-1-span-1' }, + renewedAgo: EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-1-span-7', waitForRenew: true }, + expected: CacheAction.RefreshSameRequest, + }, + { + name: 'missing renewal threshold counts as expired', + entry: { time: 1, renewalKey: 'rk', requestId: 'req-1' }, + renewedAgo: 0, + options: { requestId: 'req-2', waitForRenew: true }, + renewalKey: 'rk', + expected: CacheAction.WaitForRenew, + }, + { + name: 'entry without a timestamp counts as expired', + entry: { time: 0, renewalKey: 'rk', requestId: 'req-1' }, + renewedAgo: 0, + options: { renewalThreshold: THRESHOLD, requestId: 'req-2', waitForRenew: false }, + renewalKey: 'rk', + expected: CacheAction.RefreshBackground, + }, + { + name: 'entry without a request id is never treated as the same request', + entry: { time: 1, renewalKey: 'rk' }, + renewedAgo: EXPIRED, + options: { renewalThreshold: THRESHOLD, requestId: 'req-1', waitForRenew: true }, + renewalKey: 'rk', + expected: CacheAction.WaitForRenew, + }, + ]; + + cases.forEach(({ name, entry, renewedAgo, options, renewalKey, expected }) => { + it(name, () => { + expect(QueryCacheOpened.decideCacheAction(entry, renewedAgo, options, renewalKey)).toEqual(expected); + }); + }); +}); + +describe('QueryCache.isMemoryEntryUsable', () => { + const WINDOW = QueryCacheOpened.disablePeriod; + const HOUR = 3600; + + type Case = { + name: string, + entry: Partial, + renewedAgo: number, + expiration: number, + renewalThreshold?: number, + renewalKey?: string, + expected: boolean, + }; + + const cases: Case[] = [ + { + name: 'past expiration but still inside the in-memory window: unusable', + entry: { time: 1 }, + renewedAgo: 61 * 1000, + expiration: 60, + expected: false, + }, + { + name: 'past the in-memory window but nowhere near expiration: unusable', + entry: { time: 1 }, + renewedAgo: WINDOW + 1, + expiration: HOUR, + expected: false, + }, + { + name: 'exactly at the in-memory window: usable', + entry: { time: 1 }, + renewedAgo: WINDOW, + expiration: HOUR, + expected: true, + }, + { + name: 'exactly at expiration: usable', + entry: { time: 1 }, + renewedAgo: 60 * 1000, + expiration: 60, + expected: true, + }, + { + name: 'without a renewal key the renewal checks are skipped: no threshold and no timestamp still usable', + entry: { time: 0 }, + renewedAgo: 1000, + expiration: HOUR, + expected: true, + }, + { + name: 'renewal key without a renewal threshold: unusable', + entry: { time: 1, renewalKey: 'rk' }, + renewedAgo: 1000, + expiration: HOUR, + renewalKey: 'rk', + expected: false, + }, + { + name: 'renewal key with an entry that has no timestamp: unusable', + entry: { time: 0, renewalKey: 'rk' }, + renewedAgo: 1000, + expiration: HOUR, + renewalThreshold: HOUR, + renewalKey: 'rk', + expected: false, + }, + { + name: 'threshold below twice the window, entry inside the last window before expiry: unusable', + entry: { time: 1, renewalKey: 'rk' }, + renewedAgo: 100 * 1000, + expiration: HOUR, + renewalThreshold: 300, + renewalKey: 'rk', + expected: false, + }, + { + name: 'threshold below twice the window, entry exactly at the last window before expiry: usable', + entry: { time: 1, renewalKey: 'rk' }, + renewedAgo: 100 * 1000, + expiration: HOUR, + renewalThreshold: 400, + renewalKey: 'rk', + expected: true, + }, + { + name: 'renewal key mismatch: unusable', + entry: { time: 1, renewalKey: 'old' }, + renewedAgo: 1000, + expiration: HOUR, + renewalThreshold: HOUR, + renewalKey: 'new', + expected: false, + }, + { + name: 'every check satisfied: usable', + entry: { time: 1, renewalKey: 'rk' }, + renewedAgo: 1000, + expiration: HOUR, + renewalThreshold: HOUR, + renewalKey: 'rk', + expected: true, + }, + ]; + + cases.forEach(({ name, entry, renewedAgo, expiration, renewalThreshold, renewalKey, expected }) => { + it(name, () => { + expect( + QueryCacheOpened.isMemoryEntryUsable(entry, renewedAgo, expiration, renewalThreshold, renewalKey) + ).toEqual(expected); + }); + }); +}); diff --git a/packages/cubejs-questdb-driver/CHANGELOG.md b/packages/cubejs-questdb-driver/CHANGELOG.md index e80040a05a7dc..6f41928fda545 100644 --- a/packages/cubejs-questdb-driver/CHANGELOG.md +++ b/packages/cubejs-questdb-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/questdb-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/questdb-driver diff --git a/packages/cubejs-questdb-driver/package.json b/packages/cubejs-questdb-driver/package.json index 8fd04fda1a6c9..dcbf3eb0e7137 100644 --- a/packages/cubejs-questdb-driver/package.json +++ b/packages/cubejs-questdb-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/questdb-driver", "description": "Cube.js QuestDB database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,9 +27,9 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "@types/pg": "^8.6.0", "moment": "^2.24.0", "pg": "^8.7.0", @@ -37,8 +37,8 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "testcontainers": "^10.28.0", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-redshift-driver/CHANGELOG.md b/packages/cubejs-redshift-driver/CHANGELOG.md index bdd37a47b0bb2..bc8b92cebc0ef 100644 --- a/packages/cubejs-redshift-driver/CHANGELOG.md +++ b/packages/cubejs-redshift-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/redshift-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/redshift-driver diff --git a/packages/cubejs-redshift-driver/package.json b/packages/cubejs-redshift-driver/package.json index 25a6ba696b00d..b2f4aad2915f4 100644 --- a/packages/cubejs-redshift-driver/package.json +++ b/packages/cubejs-redshift-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/redshift-driver", "description": "Cube.js Redshift database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,13 +27,13 @@ "dependencies": { "@aws-sdk/client-redshift": "^3.22.0", "@aws-sdk/credential-providers": "^3.22.0", - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/postgres-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28" + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/postgres-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "typescript": "~5.2.2" }, "publishConfig": { diff --git a/packages/cubejs-schema-compiler/CHANGELOG.md b/packages/cubejs-schema-compiler/CHANGELOG.md index 4f6f2f2362022..dc27743aabde5 100644 --- a/packages/cubejs-schema-compiler/CHANGELOG.md +++ b/packages/cubejs-schema-compiler/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +### Bug Fixes + +- **api-gateway:** Make pre-agg build job status per-entry and data-source aware ([#11666](https://github.com/cube-js/cube/issues/11666)) ([694955f](https://github.com/cube-js/cube/commit/694955f864375515d84d463e9e7ee89a156a4a2b)) + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) ### Features diff --git a/packages/cubejs-schema-compiler/package.json b/packages/cubejs-schema-compiler/package.json index 7d3e68b78148e..c1b0f19e20f30 100644 --- a/packages/cubejs-schema-compiler/package.json +++ b/packages/cubejs-schema-compiler/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/schema-compiler", "description": "Cube schema compiler", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -40,8 +40,8 @@ "@babel/standalone": "^7.24", "@babel/traverse": "^7.24", "@babel/types": "^7.24", - "@cubejs-backend/native": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/native": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "antlr4": "^4.13.2", "camelcase": "^6.2.0", "cron-parser": "^4.9.0", @@ -60,9 +60,9 @@ }, "devDependencies": { "@clickhouse/client": "^1.12.0", - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/mssql-driver": "1.7.28", - "@cubejs-backend/query-orchestrator": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/mssql-driver": "1.7.29", + "@cubejs-backend/query-orchestrator": "1.7.29", "@types/babel__code-frame": "^7.0.6", "@types/babel__generator": "^7.6.8", "@types/babel__traverse": "^7.20.5", diff --git a/packages/cubejs-server-core/CHANGELOG.md b/packages/cubejs-server-core/CHANGELOG.md index ffed88e0060f2..6dc896fc73089 100644 --- a/packages/cubejs-server-core/CHANGELOG.md +++ b/packages/cubejs-server-core/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +### Bug Fixes + +- **api-gateway:** Make pre-agg build job status per-entry and data-source aware ([#11666](https://github.com/cube-js/cube/issues/11666)) ([694955f](https://github.com/cube-js/cube/commit/694955f864375515d84d463e9e7ee89a156a4a2b)) +- **server-core:** release orchestrators evicted from the LRU ([#11661](https://github.com/cube-js/cube/issues/11661)) ([b82c58a](https://github.com/cube-js/cube/commit/b82c58aa245919a36e3d20f79105b639d0c16863)) + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/server-core diff --git a/packages/cubejs-server-core/package.json b/packages/cubejs-server-core/package.json index e0e6fbca5a388..b4cd09f13adc9 100644 --- a/packages/cubejs-server-core/package.json +++ b/packages/cubejs-server-core/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/server-core", "description": "Cube.js base component to wire all backend components together", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,16 +29,16 @@ "unit": "jest --runInBand --forceExit --coverage dist/test" }, "dependencies": { - "@cubejs-backend/api-gateway": "1.7.28", - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/cloud": "1.7.28", - "@cubejs-backend/cubestore-driver": "1.7.28", + "@cubejs-backend/api-gateway": "1.7.29", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/cloud": "1.7.29", + "@cubejs-backend/cubestore-driver": "1.7.29", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/native": "1.7.28", - "@cubejs-backend/query-orchestrator": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", - "@cubejs-backend/templates": "1.7.28", + "@cubejs-backend/native": "1.7.29", + "@cubejs-backend/query-orchestrator": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", + "@cubejs-backend/templates": "1.7.29", "codesandbox-import-utils": "^2.1.12", "cross-spawn": "^7.0.1", "fs-extra": "^8.1.0", @@ -62,8 +62,8 @@ "ws": "^7.5.3" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-client/playground": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-client/playground": "1.7.29", "@types/cross-spawn": "^6.0.2", "@types/express": "^4.17.21", "@types/fs-extra": "^9.0.8", diff --git a/packages/cubejs-server-core/src/core/OrchestratorApi.ts b/packages/cubejs-server-core/src/core/OrchestratorApi.ts index 12e1da161c4c9..2d99755616e44 100644 --- a/packages/cubejs-server-core/src/core/OrchestratorApi.ts +++ b/packages/cubejs-server-core/src/core/OrchestratorApi.ts @@ -240,11 +240,23 @@ export class OrchestratorApi { } public async release() { - return Promise.all([ - ...Object.keys(this.seenDataSources).map(ds => this.releaseDriver(this.driverFactory, ds)), - this.releaseDriver(this.options.externalDriverFactory), - this.orchestrator.cleanup() - ]); + try { + return await Promise.all([ + ...Object.keys(this.seenDataSources).map(ds => this.releaseDriver(this.driverFactory, ds)), + this.releaseDriver(this.options.externalDriverFactory), + this.orchestrator.cleanup() + ]); + } catch (error) { + // `OrchestratorStorage` swallows this so that one orchestrator failing to + // close cannot fail shutdown, which leaves this as the only place an + // operator learns that a connection was not closed after all. + this.logger('Orchestrator Release Error', { + orchestratorId: this.options.redisPrefix, + error: (error as Error).stack || String(error), + }); + + throw error; + } } protected async releaseDriver(driverFn?: DriverFactoryByDataSource, dataSource: string = 'default') { diff --git a/packages/cubejs-server-core/src/core/OrchestratorStorage.ts b/packages/cubejs-server-core/src/core/OrchestratorStorage.ts index cc1f6de078b00..9b1b80c7947fe 100644 --- a/packages/cubejs-server-core/src/core/OrchestratorStorage.ts +++ b/packages/cubejs-server-core/src/core/OrchestratorStorage.ts @@ -4,14 +4,35 @@ import type { OrchestratorApi } from './OrchestratorApi'; export class OrchestratorStorage { protected readonly storage: LRUCache; + protected readonly pendingReleases: Set> = new Set(); + public constructor(options: { compilerCacheSize?: number, maxCompilerCacheKeepAlive?: number, updateCompilerCacheKeepAlive?: boolean } = { compilerCacheSize: 100 }) { this.storage = new LRUCache({ max: options.compilerCacheSize, ttl: options.maxCompilerCacheKeepAlive, - updateAgeOnGet: options.updateCompilerCacheKeepAlive + updateAgeOnGet: options.updateCompilerCacheKeepAlive, + // Any removal reason ('evict' | 'set' | 'delete' | 'expire') means the api is not + // reachable through the cache anymore, so its connections have to be closed. + // Without it an evicted api keeps its Cube Store web socket open forever: the heartbeat + // interval holds a reference to the socket, so it is never garbage collected either. + // disposeAfter, not dispose: release() is async and calls into the drivers, while + // dispose runs synchronously inside set()/delete(). + disposeAfter: (api: OrchestratorApi) => { + this.release(api); + }, }); } + protected release(api: OrchestratorApi) { + const pending: Promise = api.release() + .then(() => undefined, () => undefined) + .then(() => { + this.pendingReleases.delete(pending); + }); + + this.pendingReleases.add(pending); + } + public has(orchestratorId: string) { return this.storage.has(orchestratorId); } @@ -37,7 +58,9 @@ export class OrchestratorStorage { } public async releaseConnections() { - await Promise.all([...this.storage.values()].map(api => api.release())); + // clear() disposes every entry, which schedules release() for each of them. this.storage.clear(); + + await Promise.all([...this.pendingReleases]); } } diff --git a/packages/cubejs-server-core/test/unit/OrchestratorStorage.test.ts b/packages/cubejs-server-core/test/unit/OrchestratorStorage.test.ts new file mode 100644 index 0000000000000..4a2cae2ed6a36 --- /dev/null +++ b/packages/cubejs-server-core/test/unit/OrchestratorStorage.test.ts @@ -0,0 +1,160 @@ +import { OrchestratorApi } from '../../src/core/OrchestratorApi'; +import { OrchestratorStorage } from '../../src/core/OrchestratorStorage'; + +/** + * `OrchestratorStorage` only ever calls `release()` on what it holds, so the + * entry is stubbed down to that: the release lifecycle inside `OrchestratorApi` + * is its own unit. + */ +function createApi(release: () => Promise = async () => undefined) { + const api = { release: jest.fn(release) }; + + return { api: api as unknown as OrchestratorApi, release: api.release }; +} + +/** + * A real `OrchestratorApi` with a stubbed `QueryOrchestrator`, for the one case + * that is about what the api logs rather than what the storage does with it. + */ +function createRealApi(driverError: Error) { + const logger = jest.fn(); + const driver = { release: async () => { throw driverError; } }; + + const api = new OrchestratorApi( + async () => driver as any, + logger, + { + cacheAndQueueDriver: 'memory', + contextToDbType: async () => 'postgres', + contextToExternalDbType: () => 'cubestore', + redisPrefix: 'tenant-1', + } + ); + + (api as any).orchestrator = { cleanup: async () => undefined }; + + // A data source the orchestrator has touched, so the release has a driver to + // close -- and this one fails to close. + api.addDataSeenSource('default'); + + return { api, logger }; +} + +// lru-cache runs `disposeAfter` once the operation that removed the entry has +// finished, and the release it schedules is asynchronous. +const flush = () => new Promise(resolve => setImmediate(resolve)); + +describe('OrchestratorStorage', () => { + test('releases an orchestrator the LRU evicts, and keeps the one still cached', async () => { + const storage = new OrchestratorStorage({ compilerCacheSize: 1 }); + const evicted = createApi(); + const kept = createApi(); + + storage.set('evicted', evicted.api); + storage.set('kept', kept.api); + await flush(); + + expect(storage.has('evicted')).toBe(false); + expect(evicted.release).toHaveBeenCalledTimes(1); + + expect(storage.has('kept')).toBe(true); + expect(kept.release).not.toHaveBeenCalled(); + }); + + test('releases an orchestrator replaced under the same id', async () => { + const storage = new OrchestratorStorage({ compilerCacheSize: 10 }); + const first = createApi(); + const second = createApi(); + + storage.set('same-id', first.api); + storage.set('same-id', second.api); + await flush(); + + expect(first.release).toHaveBeenCalledTimes(1); + expect(second.release).not.toHaveBeenCalled(); + }); + + test('setting the same instance again does not release it', async () => { + const storage = new OrchestratorStorage({ compilerCacheSize: 10 }); + const { api, release } = createApi(); + + storage.set('same-id', api); + storage.set('same-id', api); + await flush(); + + expect(storage.has('same-id')).toBe(true); + expect(release).not.toHaveBeenCalled(); + }); + + test('releaseConnections waits for the releases it schedules', async () => { + const storage = new OrchestratorStorage({ compilerCacheSize: 10 }); + let finishRelease: () => void = () => undefined; + const { api, release } = createApi(() => new Promise(resolve => { + finishRelease = () => resolve(); + })); + + storage.set('a', api); + + let settled = false; + const releasing = storage.releaseConnections().then(() => { settled = true; }); + await flush(); + + // `clear()` only schedules the releases, so shutdown has to wait for them: + // returning here would let the process exit with the connections still open. + expect(release).toHaveBeenCalledTimes(1); + expect(settled).toBe(false); + + finishRelease(); + await releasing; + + expect(settled).toBe(true); + expect(storage.has('a')).toBe(false); + }); + + test('releaseConnections resolves and clears even when a release fails', async () => { + const storage = new OrchestratorStorage({ compilerCacheSize: 10 }); + const failing = createApi(async () => { throw new Error('driver is gone'); }); + const ok = createApi(); + + storage.set('failing', failing.api); + storage.set('ok', ok.api); + + await expect(storage.releaseConnections()).resolves.toBeUndefined(); + + expect(ok.release).toHaveBeenCalledTimes(1); + expect(storage.has('failing')).toBe(false); + expect(storage.has('ok')).toBe(false); + }); + + test('a release that fails is logged rather than lost', async () => { + const storage = new OrchestratorStorage({ compilerCacheSize: 1 }); + const { api, logger } = createRealApi(new Error('driver is gone')); + + storage.set('failing', api); + storage.set('next', createApi().api); + await flush(); + + // The storage swallows the rejection so shutdown cannot fail on it, which + // leaves the log as the only signal that a connection stayed open. + expect(logger).toHaveBeenCalledWith( + 'Orchestrator Release Error', + expect.objectContaining({ + orchestratorId: 'tenant-1', + error: expect.stringContaining('driver is gone'), + }) + ); + }); + + test('forgets a release once it has finished', async () => { + const storage = new OrchestratorStorage({ compilerCacheSize: 1 }); + + storage.set('a', createApi().api); + storage.set('b', createApi().api); + await storage.releaseConnections(); + await flush(); + + // Otherwise the set grows by one entry per eviction -- a smaller leak in + // place of the one being fixed. + expect((storage as any).pendingReleases.size).toBe(0); + }); +}); diff --git a/packages/cubejs-server/CHANGELOG.md b/packages/cubejs-server/CHANGELOG.md index 5ed77caf533f5..6e525c596e8ee 100644 --- a/packages/cubejs-server/CHANGELOG.md +++ b/packages/cubejs-server/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/server + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/server diff --git a/packages/cubejs-server/package.json b/packages/cubejs-server/package.json index 118cf4da9aaf8..5abd908f2655a 100644 --- a/packages/cubejs-server/package.json +++ b/packages/cubejs-server/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/server", "description": "Cube.js all-in-one server", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "types": "index.d.ts", "repository": { "type": "git", @@ -40,11 +40,11 @@ "jest:shapshot": "jest --updateSnapshot test" }, "dependencies": { - "@cubejs-backend/cubestore-driver": "1.7.28", + "@cubejs-backend/cubestore-driver": "1.7.29", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/native": "1.7.28", - "@cubejs-backend/server-core": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/native": "1.7.29", + "@cubejs-backend/server-core": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "@oclif/color": "^1.0.0", "@oclif/command": "^1.8.13", "@oclif/config": "^1.18.2", @@ -61,8 +61,8 @@ "ws": "^7.1.2" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/query-orchestrator": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/query-orchestrator": "1.7.29", "@oclif/dev-cli": "^1.23.1", "@types/body-parser": "^1.19.0", "@types/cors": "^2.8.8", diff --git a/packages/cubejs-snowflake-driver/CHANGELOG.md b/packages/cubejs-snowflake-driver/CHANGELOG.md index 52a016fa182a2..816c0454152bb 100644 --- a/packages/cubejs-snowflake-driver/CHANGELOG.md +++ b/packages/cubejs-snowflake-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/snowflake-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/snowflake-driver diff --git a/packages/cubejs-snowflake-driver/package.json b/packages/cubejs-snowflake-driver/package.json index bb1a62e7d40d7..608f99513d5b6 100644 --- a/packages/cubejs-snowflake-driver/package.json +++ b/packages/cubejs-snowflake-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/snowflake-driver", "description": "Cube.js Snowflake database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,8 +29,8 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.726.0", - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "snowflake-sdk": "^2.4.0" }, "license": "Apache-2.0", @@ -41,7 +41,7 @@ "extends": "../cubejs-linter" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "typescript": "~5.2.2", "vitest": "^4" } diff --git a/packages/cubejs-sqlite-driver/CHANGELOG.md b/packages/cubejs-sqlite-driver/CHANGELOG.md index 9d47520b90747..6457e155c5590 100644 --- a/packages/cubejs-sqlite-driver/CHANGELOG.md +++ b/packages/cubejs-sqlite-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/sqlite-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/sqlite-driver diff --git a/packages/cubejs-sqlite-driver/package.json b/packages/cubejs-sqlite-driver/package.json index 56d90e0fb9588..8c3111f63a37d 100644 --- a/packages/cubejs-sqlite-driver/package.json +++ b/packages/cubejs-sqlite-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/sqlite-driver", "description": "Cube.js Sqlite database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -18,13 +18,13 @@ "unit": "jest" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "sqlite3": "^5.1.7" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "jest": "^29" }, "publishConfig": { diff --git a/packages/cubejs-templates/CHANGELOG.md b/packages/cubejs-templates/CHANGELOG.md index b6be3ec1726bd..9f737c1a82367 100644 --- a/packages/cubejs-templates/CHANGELOG.md +++ b/packages/cubejs-templates/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/templates + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/templates diff --git a/packages/cubejs-templates/package.json b/packages/cubejs-templates/package.json index 489d65f155895..4d5ef4d341f09 100644 --- a/packages/cubejs-templates/package.json +++ b/packages/cubejs-templates/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/templates", - "version": "1.7.28", + "version": "1.7.29", "description": "Cube.js Templates helpers", "author": "Cube Dev, Inc.", "repository": { @@ -31,7 +31,7 @@ "extends": "../cubejs-linter" }, "dependencies": { - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/shared": "1.7.29", "cross-spawn": "^7.0.3", "fs-extra": "^9.1.0", "node-fetch": "^2.6.1", @@ -40,7 +40,7 @@ "tar": "^7.5.22" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "typescript": "~5.2.2" } } diff --git a/packages/cubejs-testing-drivers/CHANGELOG.md b/packages/cubejs-testing-drivers/CHANGELOG.md index 0f4588f3e03a5..67afdd0e66e9b 100644 --- a/packages/cubejs-testing-drivers/CHANGELOG.md +++ b/packages/cubejs-testing-drivers/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/testing-drivers + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/testing-drivers diff --git a/packages/cubejs-testing-drivers/package.json b/packages/cubejs-testing-drivers/package.json index 7fc12e08e3f20..6cf199f254894 100644 --- a/packages/cubejs-testing-drivers/package.json +++ b/packages/cubejs-testing-drivers/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/testing-drivers", - "version": "1.7.28", + "version": "1.7.29", "description": "Cube.js drivers test suite", "author": "Cube Dev, Inc.", "repository": { @@ -87,29 +87,29 @@ "dist/src" ], "dependencies": { - "@cubejs-backend/athena-driver": "1.7.28", - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/bigquery-driver": "1.7.28", - "@cubejs-backend/clickhouse-driver": "1.7.28", - "@cubejs-backend/crate-driver": "1.7.28", - "@cubejs-backend/cubestore-driver": "1.7.28", - "@cubejs-backend/databricks-jdbc-driver": "1.7.28", + "@cubejs-backend/athena-driver": "1.7.29", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/bigquery-driver": "1.7.29", + "@cubejs-backend/clickhouse-driver": "1.7.29", + "@cubejs-backend/crate-driver": "1.7.29", + "@cubejs-backend/cubestore-driver": "1.7.29", + "@cubejs-backend/databricks-jdbc-driver": "1.7.29", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/mssql-driver": "1.7.28", - "@cubejs-backend/mysql-driver": "1.7.28", - "@cubejs-backend/oracle-driver": "1.7.28", - "@cubejs-backend/pinot-driver": "1.7.28", - "@cubejs-backend/postgres-driver": "1.7.28", - "@cubejs-backend/query-orchestrator": "1.7.28", - "@cubejs-backend/questdb-driver": "1.7.28", - "@cubejs-backend/server-core": "1.7.28", - "@cubejs-backend/shared": "1.7.28", - "@cubejs-backend/snowflake-driver": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", - "@cubejs-backend/trino-driver": "1.7.28", - "@cubejs-client/core": "1.7.28", - "@cubejs-client/ws-transport": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/mssql-driver": "1.7.29", + "@cubejs-backend/mysql-driver": "1.7.29", + "@cubejs-backend/oracle-driver": "1.7.29", + "@cubejs-backend/pinot-driver": "1.7.29", + "@cubejs-backend/postgres-driver": "1.7.29", + "@cubejs-backend/query-orchestrator": "1.7.29", + "@cubejs-backend/questdb-driver": "1.7.29", + "@cubejs-backend/server-core": "1.7.29", + "@cubejs-backend/shared": "1.7.29", + "@cubejs-backend/snowflake-driver": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", + "@cubejs-backend/trino-driver": "1.7.29", + "@cubejs-client/core": "1.7.29", + "@cubejs-client/ws-transport": "1.7.29", "@jest/globals": "^29", "@types/jest": "^29", "@types/node": "^22", diff --git a/packages/cubejs-testing-shared/CHANGELOG.md b/packages/cubejs-testing-shared/CHANGELOG.md index 1e2c0946dba4c..ae15a6c6955e6 100644 --- a/packages/cubejs-testing-shared/CHANGELOG.md +++ b/packages/cubejs-testing-shared/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/testing-shared + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/testing-shared diff --git a/packages/cubejs-testing-shared/package.json b/packages/cubejs-testing-shared/package.json index 0332a59ef0e21..4ada7f9c5bbe7 100644 --- a/packages/cubejs-testing-shared/package.json +++ b/packages/cubejs-testing-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/testing-shared", - "version": "1.7.28", + "version": "1.7.29", "description": "Cube.js Testing Helpers", "author": "Cube Dev, Inc.", "repository": { @@ -26,16 +26,16 @@ ], "dependencies": { "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/query-orchestrator": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/query-orchestrator": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "@testcontainers/kafka": "~10.28.0", "dedent": "^0.7.0", "node-fetch": "^2.6.7", "testcontainers": "^10.28.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@jest/globals": "^29", "@types/dedent": "^0.7.0", "@types/jest": "^29", diff --git a/packages/cubejs-testing/CHANGELOG.md b/packages/cubejs-testing/CHANGELOG.md index 03ae39ee3f006..a5ac78f7845fc 100644 --- a/packages/cubejs-testing/CHANGELOG.md +++ b/packages/cubejs-testing/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/testing + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/testing diff --git a/packages/cubejs-testing/package.json b/packages/cubejs-testing/package.json index eab5892e7a018..78c79290a296e 100644 --- a/packages/cubejs-testing/package.json +++ b/packages/cubejs-testing/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/testing", - "version": "1.7.28", + "version": "1.7.29", "description": "Cube.js e2e tests", "author": "Cube Dev, Inc.", "repository": { @@ -92,15 +92,15 @@ "birdbox-fixtures" ], "dependencies": { - "@cubejs-backend/cubestore-driver": "1.7.28", + "@cubejs-backend/cubestore-driver": "1.7.29", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/ksql-driver": "1.7.28", - "@cubejs-backend/postgres-driver": "1.7.28", - "@cubejs-backend/query-orchestrator": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", - "@cubejs-client/ws-transport": "1.7.28", + "@cubejs-backend/ksql-driver": "1.7.29", + "@cubejs-backend/postgres-driver": "1.7.29", + "@cubejs-backend/query-orchestrator": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", + "@cubejs-client/ws-transport": "1.7.29", "dedent": "^0.7.0", "fs-extra": "^8.1.0", "http-proxy": "^1.18.1", @@ -111,8 +111,8 @@ }, "devDependencies": { "@4tw/cypress-drag-drop": "^1.6.0", - "@cubejs-backend/linter": "1.7.28", - "@cubejs-client/core": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-client/core": "1.7.29", "@jest/globals": "^29", "@types/dedent": "^0.7.0", "@types/http-proxy": "^1.17.5", diff --git a/packages/cubejs-trino-driver/CHANGELOG.md b/packages/cubejs-trino-driver/CHANGELOG.md index 75a49efbf1744..1bc0bc4c4871e 100644 --- a/packages/cubejs-trino-driver/CHANGELOG.md +++ b/packages/cubejs-trino-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/trino-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/trino-driver diff --git a/packages/cubejs-trino-driver/package.json b/packages/cubejs-trino-driver/package.json index 52574ce732d24..ac22e78d41f7c 100644 --- a/packages/cubejs-trino-driver/package.json +++ b/packages/cubejs-trino-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/trino-driver", "description": "Cube.js Trino database driver", "author": "Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,10 +28,10 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/prestodb-driver": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/prestodb-driver": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "node-fetch": "^2.6.1", "presto-client": "^1.2.0" }, @@ -40,7 +40,7 @@ "access": "public" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "jest": "^29", "testcontainers": "^10.28.0", diff --git a/packages/cubejs-vertica-driver/CHANGELOG.md b/packages/cubejs-vertica-driver/CHANGELOG.md index 7c1942b544787..24339799235d2 100644 --- a/packages/cubejs-vertica-driver/CHANGELOG.md +++ b/packages/cubejs-vertica-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/vertica-driver + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) **Note:** Version bump only for package @cubejs-backend/vertica-driver diff --git a/packages/cubejs-vertica-driver/package.json b/packages/cubejs-vertica-driver/package.json index ae1dabf1ec283..c5283b97ebae8 100644 --- a/packages/cubejs-vertica-driver/package.json +++ b/packages/cubejs-vertica-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/vertica-driver", "description": "Cube.js Vertica database driver", "author": "Eduard Karacharov, Tim Brown, Cube Dev, Inc.", - "version": "1.7.28", + "version": "1.7.29", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -19,16 +19,16 @@ "lint:fix": "eslint --fix **/*.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.28", - "@cubejs-backend/query-orchestrator": "1.7.28", - "@cubejs-backend/schema-compiler": "1.7.28", - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/base-driver": "1.7.29", + "@cubejs-backend/query-orchestrator": "1.7.29", + "@cubejs-backend/schema-compiler": "1.7.29", + "@cubejs-backend/shared": "1.7.29", "vertica-nodejs": "^1.0.3" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", - "@cubejs-backend/testing-shared": "1.7.28", + "@cubejs-backend/linter": "1.7.29", + "@cubejs-backend/testing-shared": "1.7.29", "@types/jest": "^29", "jest": "^29", "testcontainers": "^10.28.0" diff --git a/rust/cubesql/CHANGELOG.md b/rust/cubesql/CHANGELOG.md index 0c8e2df1763b1..e99357516190b 100644 --- a/rust/cubesql/CHANGELOG.md +++ b/rust/cubesql/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +**Note:** Version bump only for package @cubejs-backend/cubesql + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) ### Features diff --git a/rust/cubesql/package.json b/rust/cubesql/package.json index bb8245128fb5b..ccfd3be4127ea 100644 --- a/rust/cubesql/package.json +++ b/rust/cubesql/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/cubesql", - "version": "1.7.28", + "version": "1.7.29", "description": "SQL API for Cube as proxy over MySQL protocol.", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" diff --git a/rust/cubestore/CHANGELOG.md b/rust/cubestore/CHANGELOG.md index 986e4dfbca598..82a693ecb526a 100644 --- a/rust/cubestore/CHANGELOG.md +++ b/rust/cubestore/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.29](https://github.com/cube-js/cube/compare/v1.7.28...v1.7.29) (2026-08-27) + +### Features + +- **cubestore:** cap concurrent websocket connections per user ([#11667](https://github.com/cube-js/cube/issues/11667)) ([111eb3e](https://github.com/cube-js/cube/commit/111eb3ec049b9be1fa2f31186a7f83f58b5a0291)) + ## [1.7.28](https://github.com/cube-js/cube/compare/v1.7.27...v1.7.28) (2026-08-26) ### Bug Fixes diff --git a/rust/cubestore/Cargo.lock b/rust/cubestore/Cargo.lock index 34d2321545297..73cb99f8d5e29 100644 --- a/rust/cubestore/Cargo.lock +++ b/rust/cubestore/Cargo.lock @@ -1445,7 +1445,7 @@ dependencies = [ [[package]] name = "cubestore" -version = "1.7.28" +version = "1.7.29" dependencies = [ "actix-rt", "anyhow", diff --git a/rust/cubestore/cubestore/Cargo.toml b/rust/cubestore/cubestore/Cargo.toml index bed8b87f6f0c0..7d87c8fa35a2f 100644 --- a/rust/cubestore/cubestore/Cargo.toml +++ b/rust/cubestore/cubestore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cubestore" -version = "1.7.28" +version = "1.7.29" authors = ["Cube Dev, Inc."] edition = "2021" license = "Apache-2.0" diff --git a/rust/cubestore/package.json b/rust/cubestore/package.json index 19cb3cf9850ea..7d39be393e2cb 100644 --- a/rust/cubestore/package.json +++ b/rust/cubestore/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/cubestore", - "version": "1.7.28", + "version": "1.7.29", "description": "Cube.js pre-aggregation storage layer.", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", @@ -33,7 +33,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.28", + "@cubejs-backend/linter": "1.7.29", "@types/jest": "^29", "@types/node": "^18", "jest": "^29", @@ -43,7 +43,7 @@ "access": "public" }, "dependencies": { - "@cubejs-backend/shared": "1.7.28", + "@cubejs-backend/shared": "1.7.29", "@octokit/core": "^3.2.5", "source-map-support": "^0.5.19" },