drizzle-cube 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -213
- package/dist/client/components/AnalyticsDashboard.d.ts +1 -1
- package/dist/client/components/DashboardGrid.d.ts +1 -2
- package/dist/client/components/PortletEditModal.d.ts +1 -2
- package/dist/client/components/QueryBuilder/types.d.ts +0 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +7162 -7130
- package/dist/client/providers/CubeProvider.d.ts +6 -3
- package/dist/client/types.d.ts +0 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ Transform your Drizzle schema into a powerful, type-safe analytics platform with
|
|
|
9
9
|
|
|
10
10
|
[](https://www.npmjs.com/package/drizzle-cube)
|
|
11
11
|
[](https://www.typescriptlang.org/)
|
|
12
|
-
[](https://orm.drizzle.team/)
|
|
13
|
+
[](https://tailwindcss.com/)
|
|
13
14
|
[](https://choosealicense.com/licenses/mit/)
|
|
14
15
|
|
|
15
16
|
## Why Drizzle Cube?
|
|
@@ -39,15 +40,14 @@ export const employees = pgTable('employees', {
|
|
|
39
40
|
name: text('name').notNull(),
|
|
40
41
|
email: text('email'),
|
|
41
42
|
active: boolean('active').default(true),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
salary: integer('salary')
|
|
43
|
+
department: integer('department'),
|
|
44
|
+
organisation: integer('organisation').notNull()
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
export const departments = pgTable('departments', {
|
|
48
48
|
id: integer('id').primaryKey(),
|
|
49
49
|
name: text('name').notNull(),
|
|
50
|
-
|
|
50
|
+
organisation: integer('organisation').notNull()
|
|
51
51
|
})
|
|
52
52
|
|
|
53
53
|
export const schema = { employees, departments }
|
|
@@ -62,39 +62,23 @@ import { eq } from 'drizzle-orm'
|
|
|
62
62
|
import { schema } from './schema'
|
|
63
63
|
|
|
64
64
|
export const employeesCube = defineCube('Employees', {
|
|
65
|
-
title: 'Employee Analytics',
|
|
66
|
-
|
|
67
|
-
// Use Drizzle query structure for type safety
|
|
68
65
|
sql: (ctx) => ({
|
|
69
66
|
from: schema.employees,
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
table: schema.departments,
|
|
73
|
-
on: eq(schema.employees.departmentId, schema.departments.id),
|
|
74
|
-
type: 'left'
|
|
75
|
-
}
|
|
76
|
-
],
|
|
77
|
-
where: eq(schema.employees.organisationId, ctx.securityContext.organisationId)
|
|
67
|
+
where: eq(schema.employees.organisation, ctx.securityContext.organisationId)
|
|
78
68
|
}),
|
|
79
69
|
|
|
80
70
|
dimensions: {
|
|
81
71
|
name: {
|
|
82
72
|
name: 'name',
|
|
83
73
|
title: 'Employee Name',
|
|
84
|
-
|
|
85
|
-
|
|
74
|
+
type: 'string',
|
|
75
|
+
sql: schema.employees.name
|
|
86
76
|
},
|
|
87
77
|
email: {
|
|
88
|
-
name: 'email',
|
|
89
|
-
title: 'Email
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
departmentName: {
|
|
94
|
-
name: 'departmentName',
|
|
95
|
-
title: 'Department',
|
|
96
|
-
sql: schema.departments.name,
|
|
97
|
-
type: 'string'
|
|
78
|
+
name: 'email',
|
|
79
|
+
title: 'Email',
|
|
80
|
+
type: 'string',
|
|
81
|
+
sql: schema.employees.email
|
|
98
82
|
}
|
|
99
83
|
},
|
|
100
84
|
|
|
@@ -102,22 +86,8 @@ export const employeesCube = defineCube('Employees', {
|
|
|
102
86
|
count: {
|
|
103
87
|
name: 'count',
|
|
104
88
|
title: 'Total Employees',
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
},
|
|
108
|
-
totalSalary: {
|
|
109
|
-
name: 'totalSalary',
|
|
110
|
-
title: 'Total Salary',
|
|
111
|
-
sql: schema.employees.salary,
|
|
112
|
-
type: 'sum',
|
|
113
|
-
format: 'currency'
|
|
114
|
-
},
|
|
115
|
-
avgSalary: {
|
|
116
|
-
name: 'avgSalary',
|
|
117
|
-
title: 'Average Salary',
|
|
118
|
-
sql: schema.employees.salary,
|
|
119
|
-
type: 'avg',
|
|
120
|
-
format: 'currency'
|
|
89
|
+
type: 'count',
|
|
90
|
+
sql: schema.employees.id
|
|
121
91
|
}
|
|
122
92
|
}
|
|
123
93
|
})
|
|
@@ -126,33 +96,22 @@ export const employeesCube = defineCube('Employees', {
|
|
|
126
96
|
### 4. Setup API Server
|
|
127
97
|
|
|
128
98
|
```typescript
|
|
129
|
-
// server.ts
|
|
99
|
+
// server.ts (Hono example)
|
|
100
|
+
import { Hono } from 'hono'
|
|
130
101
|
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
131
102
|
import { createCubeApp } from 'drizzle-cube/adapters/hono'
|
|
132
|
-
import { SemanticLayerCompiler } from 'drizzle-cube/server'
|
|
133
103
|
import postgres from 'postgres'
|
|
134
104
|
import { schema } from './schema'
|
|
135
105
|
import { employeesCube } from './cubes'
|
|
136
106
|
|
|
137
|
-
|
|
138
|
-
const client = postgres(process.env.DATABASE_URL!)
|
|
139
|
-
const db = drizzle(client, { schema })
|
|
107
|
+
const db = drizzle(postgres(process.env.DATABASE_URL!), { schema })
|
|
140
108
|
|
|
141
|
-
// Create semantic layer
|
|
142
|
-
const semanticLayer = new SemanticLayerCompiler({
|
|
143
|
-
drizzle: db,
|
|
144
|
-
schema,
|
|
145
|
-
engineType: 'postgres'
|
|
146
|
-
})
|
|
147
|
-
semanticLayer.registerCube(employeesCube)
|
|
148
|
-
|
|
149
|
-
// Create API server with Cube.js compatibility
|
|
150
109
|
const app = createCubeApp({
|
|
151
|
-
|
|
110
|
+
cubes: [employeesCube],
|
|
152
111
|
drizzle: db,
|
|
153
112
|
schema,
|
|
154
|
-
getSecurityContext: async (
|
|
155
|
-
organisationId:
|
|
113
|
+
getSecurityContext: async () => ({
|
|
114
|
+
organisationId: 1 // Your auth logic here
|
|
156
115
|
})
|
|
157
116
|
})
|
|
158
117
|
|
|
@@ -162,176 +121,55 @@ export default app
|
|
|
162
121
|
### 5. Query from Frontend
|
|
163
122
|
|
|
164
123
|
```typescript
|
|
165
|
-
// Use
|
|
166
|
-
import {
|
|
167
|
-
|
|
168
|
-
function EmployeeStats() {
|
|
169
|
-
const { resultSet, isLoading } = useCubeQuery({
|
|
170
|
-
measures: ['Employees.count', 'Employees.avgSalary'],
|
|
171
|
-
dimensions: ['Employees.departmentName'],
|
|
172
|
-
filters: [
|
|
173
|
-
{ member: 'Employees.active', operator: 'equals', values: [true] }
|
|
174
|
-
]
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
if (isLoading) return <div>Loading...</div>
|
|
124
|
+
// Use built-in React components
|
|
125
|
+
import { QueryBuilder, AnalyticsDashboard } from 'drizzle-cube/client'
|
|
178
126
|
|
|
127
|
+
function App() {
|
|
179
128
|
return (
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
))}
|
|
188
|
-
</table>
|
|
129
|
+
<div>
|
|
130
|
+
<QueryBuilder apiUrl="/cubejs-api/v1" />
|
|
131
|
+
<AnalyticsDashboard
|
|
132
|
+
config={dashboardConfig}
|
|
133
|
+
baseUrl="/cubejs-api/v1"
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
189
136
|
)
|
|
190
137
|
}
|
|
191
138
|
```
|
|
192
139
|
|
|
193
140
|
## Key Features
|
|
194
141
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// ❌ Vulnerable (string concatenation)
|
|
201
|
-
const sql = `WHERE name = '${userInput}'`
|
|
202
|
-
|
|
203
|
-
// ✅ Safe (Drizzle parameterization)
|
|
204
|
-
const condition = eq(schema.employees.name, userInput)
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### 🏗️ Type Safety
|
|
208
|
-
|
|
209
|
-
Get full TypeScript support from your database schema to your analytics:
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
const cube = defineCube('Employees', {
|
|
213
|
-
dimensions: {
|
|
214
|
-
name: {
|
|
215
|
-
name: 'name',
|
|
216
|
-
title: 'Employee Name',
|
|
217
|
-
sql: schema.employees.name, // ✅ Type-safe
|
|
218
|
-
type: 'string'
|
|
219
|
-
},
|
|
220
|
-
invalid: {
|
|
221
|
-
name: 'invalid',
|
|
222
|
-
sql: schema.employees.invalidCol, // ❌ TypeScript error
|
|
223
|
-
type: 'string'
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
})
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### ⚡ Performance
|
|
230
|
-
|
|
231
|
-
- **Prepared Statements**: Drizzle generates optimized prepared statements
|
|
232
|
-
- **Query Planning**: Database optimizes repeated queries automatically
|
|
233
|
-
- **Connection Pooling**: Leverages Drizzle's connection management
|
|
234
|
-
|
|
235
|
-
### 🧩 Framework Support
|
|
236
|
-
|
|
237
|
-
Works with multiple frameworks via adapter pattern:
|
|
238
|
-
|
|
239
|
-
- **Hono** - Built-in adapter
|
|
240
|
-
- **Express** - Coming soon
|
|
241
|
-
- **Fastify** - Coming soon
|
|
242
|
-
- **Next.js** - Coming soon
|
|
243
|
-
|
|
244
|
-
## Advanced Usage
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
### Advanced Security with Row-Level Security
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
import { and, sql } from 'drizzle-orm'
|
|
251
|
-
|
|
252
|
-
const secureCube = defineCube('SecureEmployees', {
|
|
253
|
-
title: 'Secure Employee Data',
|
|
254
|
-
|
|
255
|
-
sql: (ctx) => ({
|
|
256
|
-
from: schema.employees,
|
|
257
|
-
where: and(
|
|
258
|
-
eq(schema.employees.organisationId, ctx.securityContext.organisationId),
|
|
259
|
-
// Only show employees user has permission to see
|
|
260
|
-
ctx.securityContext.role === 'admin'
|
|
261
|
-
? sql`true`
|
|
262
|
-
: eq(schema.employees.managerId, ctx.securityContext.userId)
|
|
263
|
-
)
|
|
264
|
-
}),
|
|
265
|
-
|
|
266
|
-
dimensions: {
|
|
267
|
-
name: {
|
|
268
|
-
name: 'name',
|
|
269
|
-
title: 'Employee Name',
|
|
270
|
-
sql: schema.employees.name,
|
|
271
|
-
type: 'string'
|
|
272
|
-
}
|
|
273
|
-
},
|
|
274
|
-
|
|
275
|
-
measures: {
|
|
276
|
-
count: {
|
|
277
|
-
name: 'count',
|
|
278
|
-
title: 'Employee Count',
|
|
279
|
-
sql: schema.employees.id,
|
|
280
|
-
type: 'count'
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
})
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
## API Reference
|
|
288
|
-
|
|
289
|
-
### Core Functions
|
|
290
|
-
|
|
291
|
-
- `defineCube(name, definition)` - Create type-safe cube with name and configuration
|
|
292
|
-
- `SemanticLayerCompiler({ drizzle, schema, engineType })` - Setup semantic layer compiler
|
|
293
|
-
- `createCubeApp(options)` - Create Cube.js-compatible API server
|
|
294
|
-
|
|
295
|
-
### Supported Drizzle Features
|
|
296
|
-
|
|
297
|
-
- ✅ **All Database Types** - PostgreSQL, MySQL, SQLite
|
|
298
|
-
- ✅ **Query Builder** - Full Drizzle query builder support
|
|
299
|
-
- ✅ **Schema References** - Direct column references
|
|
300
|
-
- ✅ **SQL Templates** - Raw SQL with parameterization
|
|
301
|
-
- ✅ **Aggregations** - count, sum, avg, min, max, countDistinct
|
|
302
|
-
- ✅ **Joins** - Inner, left, right, full outer joins
|
|
303
|
-
- ✅ **CTEs** - Common table expressions
|
|
304
|
-
- ✅ **Subqueries** - Nested query support
|
|
305
|
-
- ✅ **Window Functions** - Advanced analytics
|
|
306
|
-
- ✅ **JSON Operations** - PostgreSQL JSON/JSONB support
|
|
142
|
+
🔒 **SQL Injection Proof** - All queries use Drizzle's parameterized SQL
|
|
143
|
+
🛡️ **Type Safe** - Full TypeScript inference from your database schema
|
|
144
|
+
⚡ **Performance** - Prepared statements and query optimization
|
|
145
|
+
🧩 **Cube.js Compatible** - Works with existing Cube.js React components
|
|
146
|
+
🎯 **Zero Config** - Infer cube definitions from your Drizzle schema
|
|
307
147
|
|
|
308
|
-
### Filter Operators
|
|
309
148
|
|
|
310
|
-
|
|
149
|
+
## Supported Features
|
|
311
150
|
|
|
312
|
-
|
|
313
|
-
-
|
|
314
|
-
|
|
315
|
-
-
|
|
316
|
-
-
|
|
151
|
+
✅ **Multiple Database Types** - PostgreSQL, MySQL
|
|
152
|
+
✅ **Framework Adapters** - Hono, Express, Fastify, Next.js
|
|
153
|
+
✅ **Full Type Safety** - Complete TypeScript inference
|
|
154
|
+
✅ **All SQL Features** - Joins, CTEs, subqueries, window functions
|
|
155
|
+
✅ **Cube.js Compatibility** - Drop-in replacement for existing apps
|
|
317
156
|
|
|
318
157
|
## Documentation
|
|
319
158
|
|
|
320
|
-
📚 **[Full Documentation](https://www.drizzle-cube.dev/)** - Complete guides and API reference
|
|
321
|
-
🚀 **[Try the Sandbox](https://try.drizzle-cube.dev/)** - Working example version to experiment with
|
|
159
|
+
- 📚 **[Full Documentation](https://www.drizzle-cube.dev/)** - Complete guides and API reference
|
|
160
|
+
- 🚀 **[Try the Sandbox](https://try.drizzle-cube.dev/)** - Working example version to experiment with
|
|
322
161
|
|
|
323
162
|
### Local Development
|
|
324
163
|
```bash
|
|
325
|
-
|
|
326
|
-
npm run dev:help
|
|
327
|
-
|
|
328
|
-
# Build documentation site
|
|
329
|
-
npm run build:help
|
|
164
|
+
npm run dev
|
|
330
165
|
```
|
|
331
166
|
|
|
332
167
|
## Examples
|
|
333
168
|
|
|
334
|
-
- **[Hono Example](https://github.com/cliftonc/drizzle-cube/tree/main/examples/hono)** - Full-featured dashboard with Cloudflare Workers
|
|
169
|
+
- **[Hono Example](https://github.com/cliftonc/drizzle-cube/tree/main/examples/hono)** - Full-featured dashboard with Cloudflare Workers support
|
|
170
|
+
- **[Express Example](https://github.com/cliftonc/drizzle-cube/tree/main/examples/express)** - Simple Express.js server with React dashboard
|
|
171
|
+
- **[Fastify Example](https://github.com/cliftonc/drizzle-cube/tree/main/examples/fastify)** - High-performance Fastify server with React client
|
|
172
|
+
- **[Next.js Example](https://github.com/cliftonc/drizzle-cube/tree/main/examples/nextjs)** - Full-stack Next.js app with API routes
|
|
335
173
|
|
|
336
174
|
## Contributing
|
|
337
175
|
|
|
@@ -339,9 +177,6 @@ We welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md)
|
|
|
339
177
|
|
|
340
178
|
## Roadmap
|
|
341
179
|
|
|
342
|
-
- 🔄 **Express Adapter** - Express.js integration
|
|
343
|
-
- 🔄 **Fastify Adapter** - Fastify integration
|
|
344
|
-
- 🔄 **Next.js Adapter** - Next.js API routes
|
|
345
180
|
- 🔄 **Pre-aggregations** - Materialized view support
|
|
346
181
|
- 🔄 **Real-time Updates** - WebSocket support
|
|
347
182
|
- 🔄 **Query Caching** - Redis integration
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { AnalyticsDashboardProps } from '../types';
|
|
2
|
-
export default function AnalyticsDashboard({ config,
|
|
2
|
+
export default function AnalyticsDashboard({ config, editable, onConfigChange, onSave, onDirtyStateChange }: AnalyticsDashboardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -5,7 +5,6 @@ interface DashboardGridProps {
|
|
|
5
5
|
onConfigChange?: (config: DashboardConfig) => void;
|
|
6
6
|
onPortletRefresh?: (portletId: string) => void;
|
|
7
7
|
onSave?: (config: DashboardConfig) => Promise<void> | void;
|
|
8
|
-
apiUrl?: string;
|
|
9
8
|
}
|
|
10
|
-
export default function DashboardGrid({ config, editable, onConfigChange, onPortletRefresh, onSave
|
|
9
|
+
export default function DashboardGrid({ config, editable, onConfigChange, onPortletRefresh, onSave }: DashboardGridProps): import("react/jsx-runtime").JSX.Element;
|
|
11
10
|
export {};
|
|
@@ -6,7 +6,6 @@ interface PortletEditModalProps {
|
|
|
6
6
|
portlet?: PortletConfig | null;
|
|
7
7
|
title: string;
|
|
8
8
|
submitText: string;
|
|
9
|
-
apiUrl?: string;
|
|
10
9
|
}
|
|
11
|
-
export default function PortletEditModal({ isOpen, onClose, onSave, portlet, title, submitText
|
|
10
|
+
export default function PortletEditModal({ isOpen, onClose, onSave, portlet, title, submitText }: PortletEditModalProps): import("react/jsx-runtime").JSX.Element;
|
|
12
11
|
export {};
|
package/dist/client/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export { default as PortletEditModal } from './components/PortletEditModal';
|
|
|
7
7
|
export { default as DashboardEditModal } from './components/DashboardEditModal';
|
|
8
8
|
export { default as Modal } from './components/Modal';
|
|
9
9
|
export { default as QueryBuilder } from './components/QueryBuilder';
|
|
10
|
-
export { CubeProvider } from './providers/CubeProvider';
|
|
10
|
+
export { CubeProvider, useCubeContext } from './providers/CubeProvider';
|
|
11
11
|
export { useCubeQuery } from './hooks/useCubeQuery';
|
|
12
12
|
export { createCubeClient } from './client/CubeClient';
|
|
13
|
-
export type { PortletConfig, ChartType, ChartAxisConfig, ChartDisplayConfig, CubeQuery, CubeQueryOptions, DashboardConfig } from './types';
|
|
13
|
+
export type { PortletConfig, ChartType, ChartAxisConfig, ChartDisplayConfig, CubeQuery, CubeQueryOptions, CubeApiOptions, DashboardConfig } from './types';
|
|
14
14
|
export { createDashboardLayout, formatChartData } from './utils';
|