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:
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:
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 selection | Cube load query |
|---|---|
| ordinary dimension | dimensions member |
| dimension with a grain | timeDimensions member and granularity |
| measure | measures member |
| dimension filter | Cube member, operator, and string values |
| sort | ordered [member, direction] tuples |
| offset page | limit 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.