From drizzle-cube
Create and configure Drizzle Cube semantic layer cube definitions with proper security context, measures, dimensions, and joins.
How this skill is triggered — by the user, by Claude, or both
Slash command
/drizzle-cube:dc-cube-definitionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill helps you create properly structured cube definitions for Drizzle Cube's semantic layer.
This skill helps you create properly structured cube definitions for Drizzle Cube's semantic layer.
Drizzle Cube is Drizzle ORM-first. All cubes reference Drizzle schema columns directly for compile-time validation and SQL injection protection.
import { defineCube } from 'drizzle-cube/server'
import { eq } from 'drizzle-orm'
import { employees } from './schema'
export const employeesCube = defineCube({
name: 'Employees',
// REQUIRED: Security context filter for multi-tenant isolation
sql: (securityContext) => eq(employees.organisationId, securityContext.organisationId),
measures: {
count: {
type: 'count',
sql: () => employees.id
},
totalSalary: {
type: 'sum',
sql: () => employees.salary
},
averageSalary: {
type: 'avg',
sql: () => employees.salary
}
},
dimensions: {
id: {
type: 'number',
sql: () => employees.id,
primaryKey: true
},
name: {
type: 'string',
sql: () => employees.name
},
email: {
type: 'string',
sql: () => employees.email
},
createdAt: {
type: 'time',
sql: () => employees.createdAt
}
}
})
Every cube MUST implement security filtering. This is mandatory for multi-tenant data isolation.
// REQUIRED pattern - filter by security context
sql: (securityContext) => eq(table.organisationId, securityContext.organisationId)
// For multiple conditions
sql: (securityContext) => and(
eq(table.organisationId, securityContext.organisationId),
eq(table.isDeleted, false)
)
The security context is passed to every query execution:
const result = await semanticLayer.execute(query, {
organisationId: 'org-123',
userId: 'user-456'
})
| Type | Description | Example |
|---|---|---|
count | Count rows | { type: 'count', sql: () => table.id } |
countDistinct | Count unique values | { type: 'countDistinct', sql: () => table.userId } |
sum | Sum numeric values | { type: 'sum', sql: () => table.amount } |
avg | Average numeric values | { type: 'avg', sql: () => table.price } |
min | Minimum value | { type: 'min', sql: () => table.date } |
max | Maximum value | { type: 'max', sql: () => table.score } |
Apply filters within measures:
measures: {
activeCount: {
type: 'count',
sql: () => employees.id,
filters: [{ sql: () => eq(employees.isActive, true) }]
},
highValueOrders: {
type: 'sum',
sql: () => orders.amount,
filters: [{ sql: () => gt(orders.amount, 1000) }]
}
}
| Type | Description | Example |
|---|---|---|
string | Text values | { type: 'string', sql: () => table.name } |
number | Numeric values | { type: 'number', sql: () => table.quantity } |
boolean | True/false values | { type: 'boolean', sql: () => table.isActive } |
time | Date/timestamp values | { type: 'time', sql: () => table.createdAt } |
Mark the primary key for proper aggregations:
dimensions: {
id: {
type: 'number',
sql: () => table.id,
primaryKey: true // Important for multi-cube queries
}
}
| Type | Description | SQL Join |
|---|---|---|
belongsTo | Many-to-one | INNER JOIN |
hasOne | One-to-one | LEFT JOIN |
hasMany | One-to-many | LEFT JOIN (with CTE) |
belongsToMany | Many-to-many | LEFT JOIN (through junction) |
export const employeesCube = defineCube({
name: 'Employees',
sql: (ctx) => eq(employees.organisationId, ctx.organisationId),
joins: {
Departments: {
targetCube: () => departmentsCube,
relationship: 'belongsTo',
on: [
{ source: employees.departmentId, target: departments.id }
]
}
},
measures: { /* ... */ },
dimensions: { /* ... */ }
})
export const departmentsCube = defineCube({
name: 'Departments',
sql: (ctx) => eq(departments.organisationId, ctx.organisationId),
joins: {
Employees: {
targetCube: () => employeesCube,
relationship: 'hasMany',
on: [
{ source: departments.id, target: employees.departmentId }
]
}
},
measures: { /* ... */ },
dimensions: { /* ... */ }
})
Use when relating through a junction table:
export const employeesCube = defineCube({
name: 'Employees',
sql: (ctx) => eq(employees.organisationId, ctx.organisationId),
joins: {
Projects: {
targetCube: () => projectsCube,
relationship: 'belongsToMany',
on: [], // Not used for belongsToMany
through: {
table: employeeProjects, // Junction table
sourceKey: [
{ source: employees.id, target: employeeProjects.employeeId }
],
targetKey: [
{ source: employeeProjects.projectId, target: projects.id }
],
// Optional: Security filter for junction table
securitySql: (securityContext) =>
eq(employeeProjects.organisationId, securityContext.organisationId)
}
}
}
})
For fact-dimension-fact joins, the dimension cube MUST define hasMany relationships back to all fact cubes:
// Dimension cube - MUST define hasMany to both facts
export const productsCube = defineCube({
name: 'Products',
sql: (ctx) => eq(products.organisationId, ctx.organisationId),
joins: {
Sales: {
targetCube: () => salesCube,
relationship: 'hasMany',
on: [{ source: products.id, target: sales.productId }]
},
Inventory: {
targetCube: () => inventoryCube,
relationship: 'hasMany',
on: [{ source: products.id, target: inventory.productId }]
}
},
dimensions: {
name: { type: 'string', sql: () => products.name },
category: { type: 'string', sql: () => products.category }
}
})
// Fact cube #1
export const salesCube = defineCube({
name: 'Sales',
sql: (ctx) => eq(sales.organisationId, ctx.organisationId),
joins: {
Products: {
targetCube: () => productsCube,
relationship: 'belongsTo',
on: [{ source: sales.productId, target: products.id }]
}
},
measures: {
totalRevenue: { type: 'sum', sql: () => sales.revenue }
}
})
// Fact cube #2
export const inventoryCube = defineCube({
name: 'Inventory',
sql: (ctx) => eq(inventory.organisationId, ctx.organisationId),
joins: {
Products: {
targetCube: () => productsCube,
relationship: 'belongsTo',
on: [{ source: inventory.productId, target: products.id }]
}
},
measures: {
totalStock: { type: 'sum', sql: () => inventory.stockLevel }
}
})
import { SemanticLayerCompiler } from 'drizzle-cube/server'
const semanticLayer = new SemanticLayerCompiler({
drizzle: db,
schema: schema
})
// Register cubes
semanticLayer.registerCube(employeesCube)
semanticLayer.registerCube(departmentsCube)
// Execute queries
const result = await semanticLayer.execute({
measures: ['Employees.count'],
dimensions: ['Departments.name']
}, securityContext)
dimensions: {
fullName: {
type: 'string',
sql: () => sql`${employees.firstName} || ' ' || ${employees.lastName}`
}
}
dimensions: {
createdYear: {
type: 'number',
sql: () => sql`EXTRACT(YEAR FROM ${orders.createdAt})`
}
}
measures: {
completedOrders: {
type: 'count',
sql: () => orders.id,
filters: [{ sql: () => eq(orders.status, 'completed') }]
}
}
sql functionsecuritySql in belongsToMany relationshipsCubes work identically across:
The database type is auto-detected from your Drizzle instance.
npx claudepluginhub cliftonc/drizzle-cube-plugin --plugin drizzle-cubeGenerates CDS analytical star schema (cube, dimension, text views) from RAP business objects or DDIC tables for SAP analytics.
Guides creation and modification of dbt Semantic Layer YAML configs for semantic models, metrics, dimensions, entities, and time spines in latest or legacy specs.
Converts Omni Analytics topics into Snowflake Semantic View YAML definitions via the Omni API and CLI. Use for hardening BI metrics into Snowflake or bridging Omni's semantic layer with Cortex Analyst.