Skip to main content

Connect Cube

@zuilib/analytics/cube adapts the governed ZUI query contract to Cube without adding a Cube SDK to the base package. The adapter runs in trusted server code:

AnalyticsWorkspace -> host analytics route -> Cube adapter -> server Cube transport
stable ZUI IDs exact member map credentials + security context

The browser never receives Cube member discovery, credentials, or an organization-wide ZUI Cloud key.

Map stable IDs to exact Cube members

Keep the public resource schema independent of the provider. In server-only code, map every allowed ZUI ID to the exact Cube member it represents:

server/orders-cube.ts
import {
createCubeAnalyticsExecutor,
type CubeLoadResult,
type CubeTransport,
} from '@zuilib/analytics/cube'
import {ordersSchema} from '../analytics/orders-schema'
import {cubeGateway} from './cube-gateway'

const load: CubeTransport = async ({query, securityContext, signal}) => {
// cubeGateway is application-owned and closes over server credentials.
// Adapt its response to {data, annotation?, total?, queryId?, requestId?}.
return cubeGateway.load({query, securityContext, signal}) as Promise<CubeLoadResult>
}

export const queryOrdersWithCube = createCubeAnalyticsExecutor({
resources: {
orders: {
dimensions: {
createdAt: 'Orders.createdAt',
region: 'Orders.region',
},
measures: {
revenue: 'Orders.revenue',
},
},
},
getSecurityContext: ({subject, environment}) => ({
tenantId: String(subject?.attributes?.tenantId ?? ''),
userId: subject?.id ?? '',
roles: subject?.groups ?? [],
environment: environment ?? 'development',
}),
load,
})

Mappings are explicit by design. The adapter never derives a Cube member from a ZUI label, and it fails before calling Cube when a selected stable ID lacks a mapping. Keep the transport and its token or signing key in a server-only module.

Register the executor with the standard host handler:

app/api/analytics/query/route.ts
import {createNextAnalyticsHandler} from '@zuilib/analytics/server'
import {ordersSchema} from '../../../../analytics/orders-schema'
import {queryOrdersWithCube} from '../../../../server/orders-cube'
import {authenticateRequest} from '../../../../server/auth'

export const POST = createNextAnalyticsHandler({
resources: {
orders: {schema: ordersSchema, query: queryOrdersWithCube},
},
getSubject: async (request) => {
const session = await authenticateRequest(request)
return session ? {
id: session.userId,
groups: session.roles,
attributes: {tenantId: session.tenantId},
} : undefined
},
authorize: ({subject}) => Boolean(subject?.attributes?.tenantId),
})

The host authenticates and authorizes first. The Cube security context is then derived from that trusted subject for every query; browser-provided identity is never authoritative.

Query mapping

The adapter translates only supported, already-validated operations:

ZUI selectionCube load query
ordinary dimensiondimensions member
dimension with a graintimeDimensions member and granularity
measuremeasures member
dimension filterCube member, operator, and string values
sortordered [member, direction] tuples
offset pagelimit and offset

The dependency-free v1 adapter rejects generic search, totals, cursor pagination, and application-specific parameters with a stable validation_failed error before transport. Handle those features in an application-specific transport only after defining their semantics explicitly.

Cube rows use member names as keys. The adapter converts them back to stable ZUI field IDs and builds AnalyticsResult.columns from the resource schema, including labels, value types, formats, and time grains. Numeric and boolean provider values are normalized, while incompatible or missing values fail as invalid_result.

Safe failures and testing

Transport failures become execution_failed. The adapter may preserve a bounded provider code and request ID containing safe identifier characters; it does not copy the provider message, raw response, credentials, or row data into the public error. Aborts remain cancelled and forward the original AbortSignal.

Use @zuilib/analytics/testing fixtures or a fake transport in contract tests. Assert the exact generated Cube query and security context, then run the same resource schema through a REST executor to prove the UI specification does not change when the backend changes.

Continue with the analytics quickstart or application governance.