drizzle-cube 0.1.11 → 0.1.13
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/dist/adapters/express/index.d.ts +89 -0
- package/dist/adapters/express/index.js +177 -0
- package/dist/adapters/fastify/index.d.ts +88 -0
- package/dist/adapters/fastify/index.js +213 -0
- package/dist/adapters/hono/index.d.ts +41 -6
- package/dist/adapters/hono/index.js +102 -248
- package/dist/adapters/nextjs/index.d.ts +129 -0
- package/dist/adapters/nextjs/index.js +211 -0
- package/dist/adapters/utils-C3A4JGFs.js +3779 -0
- package/dist/adapters/utils.d.ts +119 -0
- package/dist/client/components/AxisDropZone.d.ts +2 -1
- package/dist/client/index.js +3874 -3791
- package/dist/client/styles.css +1 -1
- package/dist/express/index.d.ts +2 -0
- package/dist/fastify/index.d.ts +2 -0
- package/dist/hono/index.d.ts +2 -0
- package/dist/nextjs/index.d.ts +2 -0
- package/package.json +37 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Router, Request, Response, Express } from 'express';
|
|
2
|
+
import { CorsOptions } from 'cors';
|
|
3
|
+
import { SemanticQuery, SecurityContext, DatabaseExecutor, DrizzleDatabase, Cube } from '../../server';
|
|
4
|
+
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
5
|
+
import { MySql2Database } from 'drizzle-orm/mysql2';
|
|
6
|
+
import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
|
|
7
|
+
export interface ExpressAdapterOptions<TSchema extends Record<string, any> = Record<string, any>> {
|
|
8
|
+
/**
|
|
9
|
+
* Array of cube definitions to register
|
|
10
|
+
*/
|
|
11
|
+
cubes: Cube<TSchema>[];
|
|
12
|
+
/**
|
|
13
|
+
* Drizzle database instance (REQUIRED)
|
|
14
|
+
* This is the core of drizzle-cube - Drizzle ORM integration
|
|
15
|
+
* Accepts PostgreSQL, MySQL, or SQLite database instances
|
|
16
|
+
*/
|
|
17
|
+
drizzle: PostgresJsDatabase<TSchema> | MySql2Database<TSchema> | BetterSQLite3Database<TSchema> | DrizzleDatabase<TSchema>;
|
|
18
|
+
/**
|
|
19
|
+
* Database schema for type inference (RECOMMENDED)
|
|
20
|
+
* Provides full type safety for cube definitions
|
|
21
|
+
*/
|
|
22
|
+
schema?: TSchema;
|
|
23
|
+
/**
|
|
24
|
+
* Extract security context from incoming HTTP request.
|
|
25
|
+
* Called for EVERY API request to determine user permissions and multi-tenant isolation.
|
|
26
|
+
*
|
|
27
|
+
* This is your security boundary - ensure proper authentication and authorization here.
|
|
28
|
+
*
|
|
29
|
+
* @param req - Express Request object containing the incoming HTTP request
|
|
30
|
+
* @param res - Express Response object
|
|
31
|
+
* @returns Security context with organisationId, userId, roles, etc.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* extractSecurityContext: async (req, res) => {
|
|
35
|
+
* // Extract JWT from Authorization header
|
|
36
|
+
* const token = req.headers.authorization?.replace('Bearer ', '')
|
|
37
|
+
* const decoded = await verifyJWT(token)
|
|
38
|
+
*
|
|
39
|
+
* // Return context that will be available in all cube SQL functions
|
|
40
|
+
* return {
|
|
41
|
+
* organisationId: decoded.orgId,
|
|
42
|
+
* userId: decoded.userId,
|
|
43
|
+
* roles: decoded.roles
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
*/
|
|
47
|
+
extractSecurityContext: (req: Request, res: Response) => SecurityContext | Promise<SecurityContext>;
|
|
48
|
+
/**
|
|
49
|
+
* Database engine type (optional - auto-detected if not provided)
|
|
50
|
+
*/
|
|
51
|
+
engineType?: 'postgres' | 'mysql' | 'sqlite';
|
|
52
|
+
/**
|
|
53
|
+
* CORS configuration (optional)
|
|
54
|
+
*/
|
|
55
|
+
cors?: CorsOptions;
|
|
56
|
+
/**
|
|
57
|
+
* API base path (default: '/cubejs-api/v1')
|
|
58
|
+
*/
|
|
59
|
+
basePath?: string;
|
|
60
|
+
/**
|
|
61
|
+
* JSON body parser limit (default: '10mb')
|
|
62
|
+
*/
|
|
63
|
+
jsonLimit?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Create Express router for Cube.js-compatible API
|
|
67
|
+
*/
|
|
68
|
+
export declare function createCubeRouter<TSchema extends Record<string, any> = Record<string, any>>(options: ExpressAdapterOptions<TSchema>): Router;
|
|
69
|
+
/**
|
|
70
|
+
* Convenience function to mount Cube routes on an existing Express app
|
|
71
|
+
*/
|
|
72
|
+
export declare function mountCubeRoutes<TSchema extends Record<string, any> = Record<string, any>>(app: Express, options: ExpressAdapterOptions<TSchema>): Express;
|
|
73
|
+
/**
|
|
74
|
+
* Create a complete Express app with Cube.js routes
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* const app = createCubeApp({
|
|
78
|
+
* cubes: [salesCube, employeesCube],
|
|
79
|
+
* drizzle: db,
|
|
80
|
+
* schema,
|
|
81
|
+
* extractSecurityContext: async (req, res) => {
|
|
82
|
+
* const token = req.headers.authorization?.replace('Bearer ', '')
|
|
83
|
+
* const decoded = await verifyJWT(token)
|
|
84
|
+
* return { organisationId: decoded.orgId, userId: decoded.userId }
|
|
85
|
+
* }
|
|
86
|
+
* })
|
|
87
|
+
*/
|
|
88
|
+
export declare function createCubeApp<TSchema extends Record<string, any> = Record<string, any>>(options: ExpressAdapterOptions<TSchema>): Express;
|
|
89
|
+
export type { SecurityContext, DatabaseExecutor, SemanticQuery, DrizzleDatabase, CorsOptions };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import g, { Router as $ } from "express";
|
|
2
|
+
import E from "cors";
|
|
3
|
+
import { S as R, c as a, f as v, a as L, b as x, h as C } from "../utils-C3A4JGFs.js";
|
|
4
|
+
function N(y) {
|
|
5
|
+
const {
|
|
6
|
+
cubes: c,
|
|
7
|
+
drizzle: p,
|
|
8
|
+
schema: w,
|
|
9
|
+
extractSecurityContext: d,
|
|
10
|
+
engineType: Q,
|
|
11
|
+
cors: h,
|
|
12
|
+
basePath: l = "/cubejs-api/v1",
|
|
13
|
+
jsonLimit: q = "10mb"
|
|
14
|
+
} = y;
|
|
15
|
+
if (!c || c.length === 0)
|
|
16
|
+
throw new Error("At least one cube must be provided in the cubes array");
|
|
17
|
+
const u = $();
|
|
18
|
+
h && u.use(E(h)), u.use(g.json({ limit: q })), u.use(g.urlencoded({ extended: !0, limit: q }));
|
|
19
|
+
const n = new R({
|
|
20
|
+
drizzle: p,
|
|
21
|
+
schema: w,
|
|
22
|
+
engineType: Q
|
|
23
|
+
});
|
|
24
|
+
return c.forEach((t) => {
|
|
25
|
+
n.registerCube(t);
|
|
26
|
+
}), console.log(`🚀 Drizzle Cube: Registered ${c.length} cube(s) with ${Q || "auto-detected"} engine`), u.post(`${l}/load`, async (t, r) => {
|
|
27
|
+
try {
|
|
28
|
+
const e = t.body.query || t.body, s = await d(t, r), o = n.validateQuery(e);
|
|
29
|
+
if (!o.isValid)
|
|
30
|
+
return r.status(400).json(a(
|
|
31
|
+
`Query validation failed: ${o.errors.join(", ")}`,
|
|
32
|
+
400
|
|
33
|
+
));
|
|
34
|
+
const i = await n.executeMultiCubeQuery(e, s);
|
|
35
|
+
r.json(v(e, i, n));
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error("Query execution error:", e), r.status(500).json(a(
|
|
38
|
+
e instanceof Error ? e.message : "Query execution failed",
|
|
39
|
+
500
|
|
40
|
+
));
|
|
41
|
+
}
|
|
42
|
+
}), u.get(`${l}/load`, async (t, r) => {
|
|
43
|
+
try {
|
|
44
|
+
const e = t.query.query;
|
|
45
|
+
if (!e)
|
|
46
|
+
return r.status(400).json(a(
|
|
47
|
+
"Query parameter is required",
|
|
48
|
+
400
|
|
49
|
+
));
|
|
50
|
+
let s;
|
|
51
|
+
try {
|
|
52
|
+
s = JSON.parse(e);
|
|
53
|
+
} catch {
|
|
54
|
+
return r.status(400).json(a(
|
|
55
|
+
"Invalid JSON in query parameter",
|
|
56
|
+
400
|
|
57
|
+
));
|
|
58
|
+
}
|
|
59
|
+
const o = await d(t, r), i = n.validateQuery(s);
|
|
60
|
+
if (!i.isValid)
|
|
61
|
+
return r.status(400).json(a(
|
|
62
|
+
`Query validation failed: ${i.errors.join(", ")}`,
|
|
63
|
+
400
|
|
64
|
+
));
|
|
65
|
+
const f = await n.executeMultiCubeQuery(s, o);
|
|
66
|
+
r.json(v(s, f, n));
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error("Query execution error:", e), r.status(500).json(a(
|
|
69
|
+
e instanceof Error ? e.message : "Query execution failed",
|
|
70
|
+
500
|
|
71
|
+
));
|
|
72
|
+
}
|
|
73
|
+
}), u.get(`${l}/meta`, (t, r) => {
|
|
74
|
+
try {
|
|
75
|
+
const e = n.getMetadata();
|
|
76
|
+
r.json(L(e));
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error("Metadata error:", e), r.status(500).json(a(
|
|
79
|
+
e instanceof Error ? e.message : "Failed to fetch metadata",
|
|
80
|
+
500
|
|
81
|
+
));
|
|
82
|
+
}
|
|
83
|
+
}), u.post(`${l}/sql`, async (t, r) => {
|
|
84
|
+
var e, s;
|
|
85
|
+
try {
|
|
86
|
+
const o = t.body, i = await d(t, r), f = n.validateQuery(o);
|
|
87
|
+
if (!f.isValid)
|
|
88
|
+
return r.status(400).json(a(
|
|
89
|
+
`Query validation failed: ${f.errors.join(", ")}`,
|
|
90
|
+
400
|
|
91
|
+
));
|
|
92
|
+
const m = ((e = o.measures) == null ? void 0 : e[0]) || ((s = o.dimensions) == null ? void 0 : s[0]);
|
|
93
|
+
if (!m)
|
|
94
|
+
return r.status(400).json(a(
|
|
95
|
+
"No measures or dimensions specified",
|
|
96
|
+
400
|
|
97
|
+
));
|
|
98
|
+
const j = m.split(".")[0], b = await n.generateSQL(j, o, i);
|
|
99
|
+
r.json(x(o, b));
|
|
100
|
+
} catch (o) {
|
|
101
|
+
console.error("SQL generation error:", o), r.status(500).json(a(
|
|
102
|
+
o instanceof Error ? o.message : "SQL generation failed",
|
|
103
|
+
500
|
|
104
|
+
));
|
|
105
|
+
}
|
|
106
|
+
}), u.get(`${l}/sql`, async (t, r) => {
|
|
107
|
+
var e, s;
|
|
108
|
+
try {
|
|
109
|
+
const o = t.query.query;
|
|
110
|
+
if (!o)
|
|
111
|
+
return r.status(400).json(a(
|
|
112
|
+
"Query parameter is required",
|
|
113
|
+
400
|
|
114
|
+
));
|
|
115
|
+
const i = JSON.parse(o), f = await d(t, r), m = n.validateQuery(i);
|
|
116
|
+
if (!m.isValid)
|
|
117
|
+
return r.status(400).json(a(
|
|
118
|
+
`Query validation failed: ${m.errors.join(", ")}`,
|
|
119
|
+
400
|
|
120
|
+
));
|
|
121
|
+
const j = ((e = i.measures) == null ? void 0 : e[0]) || ((s = i.dimensions) == null ? void 0 : s[0]);
|
|
122
|
+
if (!j)
|
|
123
|
+
return r.status(400).json(a(
|
|
124
|
+
"No measures or dimensions specified",
|
|
125
|
+
400
|
|
126
|
+
));
|
|
127
|
+
const b = j.split(".")[0], S = await n.generateSQL(b, i, f);
|
|
128
|
+
r.json(x(i, S));
|
|
129
|
+
} catch (o) {
|
|
130
|
+
console.error("SQL generation error:", o), r.status(500).json(a(
|
|
131
|
+
o instanceof Error ? o.message : "SQL generation failed",
|
|
132
|
+
500
|
|
133
|
+
));
|
|
134
|
+
}
|
|
135
|
+
}), u.post(`${l}/dry-run`, async (t, r) => {
|
|
136
|
+
try {
|
|
137
|
+
const e = t.body.query || t.body, s = await d(t, r), o = await C(e, s, n);
|
|
138
|
+
r.json(o);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error("Dry-run error:", e), r.status(400).json({
|
|
141
|
+
error: e instanceof Error ? e.message : "Dry-run validation failed",
|
|
142
|
+
valid: !1
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}), u.get(`${l}/dry-run`, async (t, r) => {
|
|
146
|
+
try {
|
|
147
|
+
const e = t.query.query;
|
|
148
|
+
if (!e)
|
|
149
|
+
return r.status(400).json({
|
|
150
|
+
error: "Query parameter is required",
|
|
151
|
+
valid: !1
|
|
152
|
+
});
|
|
153
|
+
const s = JSON.parse(e), o = await d(t, r), i = await C(s, o, n);
|
|
154
|
+
r.json(i);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.error("Dry-run error:", e), r.status(400).json({
|
|
157
|
+
error: e instanceof Error ? e.message : "Dry-run validation failed",
|
|
158
|
+
valid: !1
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}), u.use((t, r, e, s) => {
|
|
162
|
+
console.error("Express adapter error:", t), e.headersSent || e.status(500).json(a(t, 500));
|
|
163
|
+
}), u;
|
|
164
|
+
}
|
|
165
|
+
function M(y, c) {
|
|
166
|
+
const p = N(c);
|
|
167
|
+
return y.use("/", p), y;
|
|
168
|
+
}
|
|
169
|
+
function O(y) {
|
|
170
|
+
const c = g();
|
|
171
|
+
return M(c, y);
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
O as createCubeApp,
|
|
175
|
+
N as createCubeRouter,
|
|
176
|
+
M as mountCubeRoutes
|
|
177
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { FastifyPluginCallback, FastifyRequest, FastifyInstance } from 'fastify';
|
|
2
|
+
import { FastifyCorsOptions } from '@fastify/cors';
|
|
3
|
+
import { SemanticQuery, SecurityContext, DatabaseExecutor, DrizzleDatabase, Cube } from '../../server';
|
|
4
|
+
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
5
|
+
import { MySql2Database } from 'drizzle-orm/mysql2';
|
|
6
|
+
import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
|
|
7
|
+
export interface FastifyAdapterOptions<TSchema extends Record<string, any> = Record<string, any>> {
|
|
8
|
+
/**
|
|
9
|
+
* Array of cube definitions to register
|
|
10
|
+
*/
|
|
11
|
+
cubes: Cube<TSchema>[];
|
|
12
|
+
/**
|
|
13
|
+
* Drizzle database instance (REQUIRED)
|
|
14
|
+
* This is the core of drizzle-cube - Drizzle ORM integration
|
|
15
|
+
* Accepts PostgreSQL, MySQL, or SQLite database instances
|
|
16
|
+
*/
|
|
17
|
+
drizzle: PostgresJsDatabase<TSchema> | MySql2Database<TSchema> | BetterSQLite3Database<TSchema> | DrizzleDatabase<TSchema>;
|
|
18
|
+
/**
|
|
19
|
+
* Database schema for type inference (RECOMMENDED)
|
|
20
|
+
* Provides full type safety for cube definitions
|
|
21
|
+
*/
|
|
22
|
+
schema?: TSchema;
|
|
23
|
+
/**
|
|
24
|
+
* Extract security context from incoming HTTP request.
|
|
25
|
+
* Called for EVERY API request to determine user permissions and multi-tenant isolation.
|
|
26
|
+
*
|
|
27
|
+
* This is your security boundary - ensure proper authentication and authorization here.
|
|
28
|
+
*
|
|
29
|
+
* @param request - Fastify Request object containing the incoming HTTP request
|
|
30
|
+
* @returns Security context with organisationId, userId, roles, etc.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* extractSecurityContext: async (request) => {
|
|
34
|
+
* // Extract JWT from Authorization header
|
|
35
|
+
* const token = request.headers.authorization?.replace('Bearer ', '')
|
|
36
|
+
* const decoded = await verifyJWT(token)
|
|
37
|
+
*
|
|
38
|
+
* // Return context that will be available in all cube SQL functions
|
|
39
|
+
* return {
|
|
40
|
+
* organisationId: decoded.orgId,
|
|
41
|
+
* userId: decoded.userId,
|
|
42
|
+
* roles: decoded.roles
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
extractSecurityContext: (request: FastifyRequest) => SecurityContext | Promise<SecurityContext>;
|
|
47
|
+
/**
|
|
48
|
+
* Database engine type (optional - auto-detected if not provided)
|
|
49
|
+
*/
|
|
50
|
+
engineType?: 'postgres' | 'mysql' | 'sqlite';
|
|
51
|
+
/**
|
|
52
|
+
* CORS configuration (optional)
|
|
53
|
+
*/
|
|
54
|
+
cors?: FastifyCorsOptions;
|
|
55
|
+
/**
|
|
56
|
+
* API base path (default: '/cubejs-api/v1')
|
|
57
|
+
*/
|
|
58
|
+
basePath?: string;
|
|
59
|
+
/**
|
|
60
|
+
* JSON body parser limit (default: 10485760 - 10MB)
|
|
61
|
+
*/
|
|
62
|
+
bodyLimit?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Fastify plugin for Cube.js-compatible API
|
|
66
|
+
*/
|
|
67
|
+
export declare const cubePlugin: FastifyPluginCallback<FastifyAdapterOptions>;
|
|
68
|
+
/**
|
|
69
|
+
* Helper function to register cube routes on an existing Fastify instance
|
|
70
|
+
*/
|
|
71
|
+
export declare function registerCubeRoutes<TSchema extends Record<string, any> = Record<string, any>>(fastify: FastifyInstance, options: FastifyAdapterOptions<TSchema>): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Create a complete Fastify instance with Cube.js routes
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const app = createCubeApp({
|
|
77
|
+
* cubes: [salesCube, employeesCube],
|
|
78
|
+
* drizzle: db,
|
|
79
|
+
* schema,
|
|
80
|
+
* extractSecurityContext: async (request) => {
|
|
81
|
+
* const token = request.headers.authorization?.replace('Bearer ', '')
|
|
82
|
+
* const decoded = await verifyJWT(token)
|
|
83
|
+
* return { organisationId: decoded.orgId, userId: decoded.userId }
|
|
84
|
+
* }
|
|
85
|
+
* })
|
|
86
|
+
*/
|
|
87
|
+
export declare function createCubeApp<TSchema extends Record<string, any> = Record<string, any>>(options: FastifyAdapterOptions<TSchema>): FastifyInstance;
|
|
88
|
+
export type { SecurityContext, DatabaseExecutor, SemanticQuery, DrizzleDatabase, FastifyCorsOptions };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { S as P, c as u, f as Q, a as R, b as C, h as w } from "../utils-C3A4JGFs.js";
|
|
2
|
+
const x = function(o, S, h) {
|
|
3
|
+
const {
|
|
4
|
+
cubes: m,
|
|
5
|
+
drizzle: $,
|
|
6
|
+
schema: E,
|
|
7
|
+
extractSecurityContext: y,
|
|
8
|
+
engineType: q,
|
|
9
|
+
cors: v,
|
|
10
|
+
basePath: d = "/cubejs-api/v1",
|
|
11
|
+
bodyLimit: p = 10485760
|
|
12
|
+
// 10MB
|
|
13
|
+
} = S;
|
|
14
|
+
if (!m || m.length === 0)
|
|
15
|
+
return h(new Error("At least one cube must be provided in the cubes array"));
|
|
16
|
+
v && o.register(import("@fastify/cors"), v), o.addHook("onRequest", async (r, t) => {
|
|
17
|
+
r.method === "POST" && (r.body = void 0);
|
|
18
|
+
});
|
|
19
|
+
const i = new P({
|
|
20
|
+
drizzle: $,
|
|
21
|
+
schema: E,
|
|
22
|
+
engineType: q
|
|
23
|
+
});
|
|
24
|
+
m.forEach((r) => {
|
|
25
|
+
i.registerCube(r);
|
|
26
|
+
}), console.log(`🚀 Drizzle Cube: Registered ${m.length} cube(s) with ${q || "auto-detected"} engine`), o.post(`${d}/load`, {
|
|
27
|
+
bodyLimit: p,
|
|
28
|
+
schema: {
|
|
29
|
+
body: {
|
|
30
|
+
type: "object",
|
|
31
|
+
additionalProperties: !0
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}, async (r, t) => {
|
|
35
|
+
try {
|
|
36
|
+
const e = r.body, a = e.query || e, s = await y(r), n = i.validateQuery(a);
|
|
37
|
+
if (!n.isValid)
|
|
38
|
+
return t.status(400).send(u(
|
|
39
|
+
`Query validation failed: ${n.errors.join(", ")}`,
|
|
40
|
+
400
|
|
41
|
+
));
|
|
42
|
+
const c = await i.executeMultiCubeQuery(a, s);
|
|
43
|
+
return Q(a, c, i);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return r.log.error(e, "Query execution error"), t.status(500).send(u(
|
|
46
|
+
e instanceof Error ? e.message : "Query execution failed",
|
|
47
|
+
500
|
|
48
|
+
));
|
|
49
|
+
}
|
|
50
|
+
}), o.get(`${d}/load`, {
|
|
51
|
+
schema: {
|
|
52
|
+
querystring: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
query: { type: "string" }
|
|
56
|
+
},
|
|
57
|
+
required: ["query"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}, async (r, t) => {
|
|
61
|
+
try {
|
|
62
|
+
const { query: e } = r.query;
|
|
63
|
+
let a;
|
|
64
|
+
try {
|
|
65
|
+
a = JSON.parse(e);
|
|
66
|
+
} catch {
|
|
67
|
+
return t.status(400).send(u(
|
|
68
|
+
"Invalid JSON in query parameter",
|
|
69
|
+
400
|
|
70
|
+
));
|
|
71
|
+
}
|
|
72
|
+
const s = await y(r), n = i.validateQuery(a);
|
|
73
|
+
if (!n.isValid)
|
|
74
|
+
return t.status(400).send(u(
|
|
75
|
+
`Query validation failed: ${n.errors.join(", ")}`,
|
|
76
|
+
400
|
|
77
|
+
));
|
|
78
|
+
const c = await i.executeMultiCubeQuery(a, s);
|
|
79
|
+
return Q(a, c, i);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return r.log.error(e, "Query execution error"), t.status(500).send(u(
|
|
82
|
+
e instanceof Error ? e.message : "Query execution failed",
|
|
83
|
+
500
|
|
84
|
+
));
|
|
85
|
+
}
|
|
86
|
+
}), o.get(`${d}/meta`, async (r, t) => {
|
|
87
|
+
try {
|
|
88
|
+
const e = i.getMetadata();
|
|
89
|
+
return R(e);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
return r.log.error(e, "Metadata error"), t.status(500).send(u(
|
|
92
|
+
e instanceof Error ? e.message : "Failed to fetch metadata",
|
|
93
|
+
500
|
|
94
|
+
));
|
|
95
|
+
}
|
|
96
|
+
}), o.post(`${d}/sql`, {
|
|
97
|
+
bodyLimit: p,
|
|
98
|
+
schema: {
|
|
99
|
+
body: {
|
|
100
|
+
type: "object",
|
|
101
|
+
additionalProperties: !0
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, async (r, t) => {
|
|
105
|
+
var e, a;
|
|
106
|
+
try {
|
|
107
|
+
const s = r.body, n = await y(r), c = i.validateQuery(s);
|
|
108
|
+
if (!c.isValid)
|
|
109
|
+
return t.status(400).send(u(
|
|
110
|
+
`Query validation failed: ${c.errors.join(", ")}`,
|
|
111
|
+
400
|
|
112
|
+
));
|
|
113
|
+
const l = ((e = s.measures) == null ? void 0 : e[0]) || ((a = s.dimensions) == null ? void 0 : a[0]);
|
|
114
|
+
if (!l)
|
|
115
|
+
return t.status(400).send(u(
|
|
116
|
+
"No measures or dimensions specified",
|
|
117
|
+
400
|
|
118
|
+
));
|
|
119
|
+
const b = l.split(".")[0], f = await i.generateSQL(b, s, n);
|
|
120
|
+
return C(s, f);
|
|
121
|
+
} catch (s) {
|
|
122
|
+
return r.log.error(s, "SQL generation error"), t.status(500).send(u(
|
|
123
|
+
s instanceof Error ? s.message : "SQL generation failed",
|
|
124
|
+
500
|
|
125
|
+
));
|
|
126
|
+
}
|
|
127
|
+
}), o.get(`${d}/sql`, {
|
|
128
|
+
schema: {
|
|
129
|
+
querystring: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
query: { type: "string" }
|
|
133
|
+
},
|
|
134
|
+
required: ["query"]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}, async (r, t) => {
|
|
138
|
+
var e, a;
|
|
139
|
+
try {
|
|
140
|
+
const { query: s } = r.query, n = JSON.parse(s), c = await y(r), l = i.validateQuery(n);
|
|
141
|
+
if (!l.isValid)
|
|
142
|
+
return t.status(400).send(u(
|
|
143
|
+
`Query validation failed: ${l.errors.join(", ")}`,
|
|
144
|
+
400
|
|
145
|
+
));
|
|
146
|
+
const b = ((e = n.measures) == null ? void 0 : e[0]) || ((a = n.dimensions) == null ? void 0 : a[0]);
|
|
147
|
+
if (!b)
|
|
148
|
+
return t.status(400).send(u(
|
|
149
|
+
"No measures or dimensions specified",
|
|
150
|
+
400
|
|
151
|
+
));
|
|
152
|
+
const f = b.split(".")[0], j = await i.generateSQL(f, n, c);
|
|
153
|
+
return C(n, j);
|
|
154
|
+
} catch (s) {
|
|
155
|
+
return r.log.error(s, "SQL generation error"), t.status(500).send(u(
|
|
156
|
+
s instanceof Error ? s.message : "SQL generation failed",
|
|
157
|
+
500
|
|
158
|
+
));
|
|
159
|
+
}
|
|
160
|
+
}), o.post(`${d}/dry-run`, {
|
|
161
|
+
bodyLimit: p,
|
|
162
|
+
schema: {
|
|
163
|
+
body: {
|
|
164
|
+
type: "object",
|
|
165
|
+
additionalProperties: !0
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}, async (r, t) => {
|
|
169
|
+
try {
|
|
170
|
+
const e = r.body, a = e.query || e, s = await y(r);
|
|
171
|
+
return await w(a, s, i);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
return r.log.error(e, "Dry-run error"), t.status(400).send({
|
|
174
|
+
error: e instanceof Error ? e.message : "Dry-run validation failed",
|
|
175
|
+
valid: !1
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}), o.get(`${d}/dry-run`, {
|
|
179
|
+
schema: {
|
|
180
|
+
querystring: {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: {
|
|
183
|
+
query: { type: "string" }
|
|
184
|
+
},
|
|
185
|
+
required: ["query"]
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}, async (r, t) => {
|
|
189
|
+
try {
|
|
190
|
+
const { query: e } = r.query, a = JSON.parse(e), s = await y(r);
|
|
191
|
+
return await w(a, s, i);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
return r.log.error(e, "Dry-run error"), t.status(400).send({
|
|
194
|
+
error: e instanceof Error ? e.message : "Dry-run validation failed",
|
|
195
|
+
valid: !1
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}), o.setErrorHandler(async (r, t, e) => (t.log.error(r, "Fastify cube adapter error"), e.statusCode < 400 && e.status(500), u(r, e.statusCode))), h();
|
|
199
|
+
};
|
|
200
|
+
async function N(g, o) {
|
|
201
|
+
await g.register(x, o);
|
|
202
|
+
}
|
|
203
|
+
function M(g) {
|
|
204
|
+
const o = require("fastify")({
|
|
205
|
+
logger: !0
|
|
206
|
+
});
|
|
207
|
+
return o.register(x, g), o;
|
|
208
|
+
}
|
|
209
|
+
export {
|
|
210
|
+
M as createCubeApp,
|
|
211
|
+
x as cubePlugin,
|
|
212
|
+
N as registerCubeRoutes
|
|
213
|
+
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import {
|
|
2
|
+
import { SemanticQuery, SecurityContext, DatabaseExecutor, DrizzleDatabase, Cube } from '../../server';
|
|
3
3
|
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
4
4
|
import { MySql2Database } from 'drizzle-orm/mysql2';
|
|
5
5
|
import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
|
|
6
6
|
export interface HonoAdapterOptions<TSchema extends Record<string, any> = Record<string, any>> {
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Array of cube definitions to register
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
cubes: Cube<TSchema>[];
|
|
11
11
|
/**
|
|
12
12
|
* Drizzle database instance (REQUIRED)
|
|
13
13
|
* This is the core of drizzle-cube - Drizzle ORM integration
|
|
@@ -20,10 +20,33 @@ export interface HonoAdapterOptions<TSchema extends Record<string, any> = Record
|
|
|
20
20
|
*/
|
|
21
21
|
schema?: TSchema;
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Extract security context from incoming HTTP request.
|
|
24
|
+
* Called for EVERY API request to determine user permissions and multi-tenant isolation.
|
|
25
|
+
*
|
|
26
|
+
* This is your security boundary - ensure proper authentication and authorization here.
|
|
27
|
+
*
|
|
28
|
+
* @param c - Hono context containing the incoming HTTP request
|
|
29
|
+
* @returns Security context with organisationId, userId, roles, etc.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* extractSecurityContext: async (c) => {
|
|
33
|
+
* // Extract JWT from Authorization header
|
|
34
|
+
* const token = c.req.header('Authorization')?.replace('Bearer ', '')
|
|
35
|
+
* const decoded = await verifyJWT(token)
|
|
36
|
+
*
|
|
37
|
+
* // Return context that will be available in all cube SQL functions
|
|
38
|
+
* return {
|
|
39
|
+
* organisationId: decoded.orgId,
|
|
40
|
+
* userId: decoded.userId,
|
|
41
|
+
* roles: decoded.roles
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
25
44
|
*/
|
|
26
|
-
|
|
45
|
+
extractSecurityContext: (c: any) => SecurityContext | Promise<SecurityContext>;
|
|
46
|
+
/**
|
|
47
|
+
* Database engine type (optional - auto-detected if not provided)
|
|
48
|
+
*/
|
|
49
|
+
engineType?: 'postgres' | 'mysql' | 'sqlite';
|
|
27
50
|
/**
|
|
28
51
|
* CORS configuration (optional)
|
|
29
52
|
*/
|
|
@@ -48,6 +71,18 @@ export declare function createCubeRoutes<TSchema extends Record<string, any> = R
|
|
|
48
71
|
export declare function mountCubeRoutes<TSchema extends Record<string, any> = Record<string, any>>(app: Hono, options: HonoAdapterOptions<TSchema>): Hono<import('hono/types').BlankEnv, import('hono/types').BlankSchema, "/">;
|
|
49
72
|
/**
|
|
50
73
|
* Create a complete Hono app with Cube.js routes
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const app = createCubeApp({
|
|
77
|
+
* cubes: [salesCube, employeesCube],
|
|
78
|
+
* drizzle: db,
|
|
79
|
+
* schema,
|
|
80
|
+
* extractSecurityContext: async (c) => {
|
|
81
|
+
* const token = c.req.header('Authorization')
|
|
82
|
+
* const decoded = await verifyJWT(token)
|
|
83
|
+
* return { organisationId: decoded.orgId, userId: decoded.userId }
|
|
84
|
+
* }
|
|
85
|
+
* })
|
|
51
86
|
*/
|
|
52
87
|
export declare function createCubeApp<TSchema extends Record<string, any> = Record<string, any>>(options: HonoAdapterOptions<TSchema>): Hono<import('hono/types').BlankEnv, import('hono/types').BlankSchema, "/">;
|
|
53
88
|
export type { SecurityContext, DatabaseExecutor, SemanticQuery, DrizzleDatabase };
|