drizzle-cube 0.1.0 → 0.1.2

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 ADDED
@@ -0,0 +1,407 @@
1
+ # 🐲 Drizzle Cube
2
+
3
+ **Drizzle ORM-first semantic layer with Cube.js compatibility**
4
+
5
+ Transform your Drizzle schema into a powerful, type-safe analytics platform with SQL injection protection and full TypeScript support.
6
+
7
+ [![NPM Version](https://img.shields.io/npm/v/drizzle-cube)](https://www.npmjs.com/package/drizzle-cube)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)](https://www.typescriptlang.org/)
9
+ [![Drizzle ORM](https://img.shields.io/badge/Drizzle%20ORM-0.33+-green)](https://orm.drizzle.team/)
10
+ [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)
11
+
12
+ ## Why Drizzle Cube?
13
+
14
+ 🔒 **SQL Injection Proof** - All queries use Drizzle's parameterized SQL
15
+ 🛡️ **Type Safe** - Full TypeScript inference from your database schema
16
+ ⚡ **Performance** - Prepared statements and query optimization
17
+ 🧩 **Cube.js Compatible** - Works with existing Cube.js React components
18
+ 🎯 **Zero Config** - Infer cube definitions from your Drizzle schema
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Install
23
+
24
+ ```bash
25
+ npm install drizzle-cube drizzle-orm
26
+ ```
27
+
28
+ ### 2. Define Your Schema
29
+
30
+ ```typescript
31
+ // schema.ts
32
+ import { pgTable, text, integer, boolean } from 'drizzle-orm/pg-core'
33
+
34
+ export const employees = pgTable('employees', {
35
+ id: integer('id').primaryKey(),
36
+ name: text('name').notNull(),
37
+ email: text('email'),
38
+ active: boolean('active').default(true),
39
+ departmentId: integer('department_id'),
40
+ organisationId: integer('organisation_id').notNull(),
41
+ salary: integer('salary')
42
+ })
43
+
44
+ export const departments = pgTable('departments', {
45
+ id: integer('id').primaryKey(),
46
+ name: text('name').notNull(),
47
+ organisationId: integer('organisation_id').notNull()
48
+ })
49
+
50
+ export const schema = { employees, departments }
51
+ ```
52
+
53
+ ### 3. Create Type-Safe Cubes
54
+
55
+ ```typescript
56
+ // cubes.ts
57
+ import { defineCube } from 'drizzle-cube/server'
58
+ import { eq } from 'drizzle-orm'
59
+ import { schema } from './schema'
60
+
61
+ export const employeesCube = defineCube('Employees', {
62
+ title: 'Employee Analytics',
63
+
64
+ // Use Drizzle query structure for type safety
65
+ sql: (ctx) => ({
66
+ from: schema.employees,
67
+ joins: [
68
+ {
69
+ table: schema.departments,
70
+ on: eq(schema.employees.departmentId, schema.departments.id),
71
+ type: 'left'
72
+ }
73
+ ],
74
+ where: eq(schema.employees.organisationId, ctx.securityContext.organisationId)
75
+ }),
76
+
77
+ dimensions: {
78
+ name: {
79
+ name: 'name',
80
+ title: 'Employee Name',
81
+ sql: schema.employees.name,
82
+ type: 'string'
83
+ },
84
+ email: {
85
+ name: 'email',
86
+ title: 'Email Address',
87
+ sql: schema.employees.email,
88
+ type: 'string'
89
+ },
90
+ departmentName: {
91
+ name: 'departmentName',
92
+ title: 'Department',
93
+ sql: schema.departments.name,
94
+ type: 'string'
95
+ }
96
+ },
97
+
98
+ measures: {
99
+ count: {
100
+ name: 'count',
101
+ title: 'Total Employees',
102
+ sql: schema.employees.id,
103
+ type: 'count'
104
+ },
105
+ totalSalary: {
106
+ name: 'totalSalary',
107
+ title: 'Total Salary',
108
+ sql: schema.employees.salary,
109
+ type: 'sum',
110
+ format: 'currency'
111
+ },
112
+ avgSalary: {
113
+ name: 'avgSalary',
114
+ title: 'Average Salary',
115
+ sql: schema.employees.salary,
116
+ type: 'avg',
117
+ format: 'currency'
118
+ }
119
+ }
120
+ })
121
+ ```
122
+
123
+ ### 4. Setup API Server
124
+
125
+ ```typescript
126
+ // server.ts
127
+ import { drizzle } from 'drizzle-orm/postgres-js'
128
+ import { createCubeApp } from 'drizzle-cube/adapters/hono'
129
+ import { SemanticLayerCompiler } from 'drizzle-cube/server'
130
+ import postgres from 'postgres'
131
+ import { schema } from './schema'
132
+ import { employeesCube } from './cubes'
133
+
134
+ // Setup Drizzle
135
+ const client = postgres(process.env.DATABASE_URL!)
136
+ const db = drizzle(client, { schema })
137
+
138
+ // Create semantic layer
139
+ const semanticLayer = new SemanticLayerCompiler({
140
+ drizzle: db,
141
+ schema,
142
+ engineType: 'postgres'
143
+ })
144
+ semanticLayer.registerCube(employeesCube)
145
+
146
+ // Create API server with Cube.js compatibility
147
+ const app = createCubeApp({
148
+ semanticLayer,
149
+ drizzle: db,
150
+ schema,
151
+ getSecurityContext: async (c) => ({
152
+ organisationId: c.get('user')?.organisationId
153
+ })
154
+ })
155
+
156
+ export default app
157
+ ```
158
+
159
+ ### 5. Query from Frontend
160
+
161
+ ```typescript
162
+ // Use with Cube.js React SDK
163
+ import { useCubeQuery } from '@cubejs-client/react'
164
+
165
+ function EmployeeStats() {
166
+ const { resultSet, isLoading } = useCubeQuery({
167
+ measures: ['Employees.count', 'Employees.avgSalary'],
168
+ dimensions: ['Employees.departmentName'],
169
+ filters: [
170
+ { member: 'Employees.active', operator: 'equals', values: [true] }
171
+ ]
172
+ })
173
+
174
+ if (isLoading) return <div>Loading...</div>
175
+
176
+ return (
177
+ <table>
178
+ {resultSet.tablePivot().map((row, i) => (
179
+ <tr key={i}>
180
+ <td>{row['Employees.departmentName']}</td>
181
+ <td>{row['Employees.count']}</td>
182
+ <td>${row['Employees.avgSalary']}</td>
183
+ </tr>
184
+ ))}
185
+ </table>
186
+ )
187
+ }
188
+ ```
189
+
190
+ ## Key Features
191
+
192
+ ### 🔐 Security First
193
+
194
+ All SQL is generated using Drizzle's parameterized queries, making SQL injection impossible:
195
+
196
+ ```typescript
197
+ // ❌ Vulnerable (string concatenation)
198
+ const sql = `WHERE name = '${userInput}'`
199
+
200
+ // ✅ Safe (Drizzle parameterization)
201
+ const condition = eq(schema.employees.name, userInput)
202
+ ```
203
+
204
+ ### 🏗️ Type Safety
205
+
206
+ Get full TypeScript support from your database schema to your analytics:
207
+
208
+ ```typescript
209
+ const cube = defineCube('Employees', {
210
+ dimensions: {
211
+ name: {
212
+ name: 'name',
213
+ title: 'Employee Name',
214
+ sql: schema.employees.name, // ✅ Type-safe
215
+ type: 'string'
216
+ },
217
+ invalid: {
218
+ name: 'invalid',
219
+ sql: schema.employees.invalidCol, // ❌ TypeScript error
220
+ type: 'string'
221
+ }
222
+ }
223
+ })
224
+ ```
225
+
226
+ ### ⚡ Performance
227
+
228
+ - **Prepared Statements**: Drizzle generates optimized prepared statements
229
+ - **Query Planning**: Database optimizes repeated queries automatically
230
+ - **Connection Pooling**: Leverages Drizzle's connection management
231
+
232
+ ### 🧩 Framework Support
233
+
234
+ Works with multiple frameworks via adapter pattern:
235
+
236
+ - **Hono** - Built-in adapter
237
+ - **Express** - Coming soon
238
+ - **Fastify** - Coming soon
239
+ - **Next.js** - Coming soon
240
+
241
+ ## Advanced Usage
242
+
243
+ ### Complex Queries with CTEs
244
+
245
+ ```typescript
246
+ import { sql } from 'drizzle-orm'
247
+
248
+ const advancedCube = defineCube('DepartmentAnalytics', {
249
+ title: 'Department Analytics',
250
+
251
+ // For complex queries, you can use raw SQL
252
+ sql: (ctx) => sql`
253
+ WITH department_stats AS (
254
+ SELECT
255
+ d.id,
256
+ d.name,
257
+ COUNT(e.id) as employee_count,
258
+ AVG(e.salary) as avg_salary
259
+ FROM ${schema.departments} d
260
+ LEFT JOIN ${schema.employees} e ON d.id = e.department_id
261
+ WHERE d.organisation_id = ${ctx.securityContext.organisationId}
262
+ GROUP BY d.id, d.name
263
+ )
264
+ SELECT * FROM department_stats
265
+ `,
266
+
267
+ dimensions: {
268
+ name: {
269
+ name: 'name',
270
+ title: 'Department Name',
271
+ sql: sql`name`,
272
+ type: 'string'
273
+ }
274
+ },
275
+
276
+ measures: {
277
+ employeeCount: {
278
+ name: 'employeeCount',
279
+ title: 'Employee Count',
280
+ sql: sql`employee_count`,
281
+ type: 'number'
282
+ },
283
+ avgSalary: {
284
+ name: 'avgSalary',
285
+ title: 'Average Salary',
286
+ sql: sql`avg_salary`,
287
+ type: 'number',
288
+ format: 'currency'
289
+ }
290
+ }
291
+ })
292
+ ```
293
+
294
+ ### Advanced Security with Row-Level Security
295
+
296
+ ```typescript
297
+ import { and, sql } from 'drizzle-orm'
298
+
299
+ const secureCube = defineCube('SecureEmployees', {
300
+ title: 'Secure Employee Data',
301
+
302
+ sql: (ctx) => ({
303
+ from: schema.employees,
304
+ where: and(
305
+ eq(schema.employees.organisationId, ctx.securityContext.organisationId),
306
+ // Only show employees user has permission to see
307
+ ctx.securityContext.role === 'admin'
308
+ ? sql`true`
309
+ : eq(schema.employees.managerId, ctx.securityContext.userId)
310
+ )
311
+ }),
312
+
313
+ dimensions: {
314
+ name: {
315
+ name: 'name',
316
+ title: 'Employee Name',
317
+ sql: schema.employees.name,
318
+ type: 'string'
319
+ }
320
+ },
321
+
322
+ measures: {
323
+ count: {
324
+ name: 'count',
325
+ title: 'Employee Count',
326
+ sql: schema.employees.id,
327
+ type: 'count'
328
+ }
329
+ }
330
+ })
331
+ ```
332
+
333
+ ### Multiple Database Support
334
+
335
+ ```typescript
336
+ // PostgreSQL
337
+ import { drizzle } from 'drizzle-orm/postgres-js'
338
+ import postgres from 'postgres'
339
+
340
+ // MySQL
341
+ import { drizzle } from 'drizzle-orm/mysql2'
342
+ import mysql from 'mysql2/promise'
343
+
344
+ // SQLite
345
+ import { drizzle } from 'drizzle-orm/better-sqlite3'
346
+ import Database from 'better-sqlite3'
347
+ ```
348
+
349
+ ## API Reference
350
+
351
+ ### Core Functions
352
+
353
+ - `defineCube(name, definition)` - Create type-safe cube with name and configuration
354
+ - `SemanticLayerCompiler({ drizzle, schema, engineType })` - Setup semantic layer compiler
355
+ - `createCubeApp(options)` - Create Cube.js-compatible API server
356
+
357
+ ### Supported Drizzle Features
358
+
359
+ - ✅ **All Database Types** - PostgreSQL, MySQL, SQLite
360
+ - ✅ **Query Builder** - Full Drizzle query builder support
361
+ - ✅ **Schema References** - Direct column references
362
+ - ✅ **SQL Templates** - Raw SQL with parameterization
363
+ - ✅ **Aggregations** - count, sum, avg, min, max, countDistinct
364
+ - ✅ **Joins** - Inner, left, right, full outer joins
365
+ - ✅ **CTEs** - Common table expressions
366
+ - ✅ **Subqueries** - Nested query support
367
+ - ✅ **Window Functions** - Advanced analytics
368
+ - ✅ **JSON Operations** - PostgreSQL JSON/JSONB support
369
+
370
+ ### Filter Operators
371
+
372
+ Supports all Cube.js filter operators with Drizzle safety:
373
+
374
+ - `equals`, `notEquals` → `eq()`, `ne()`
375
+ - `contains`, `notContains` → `ilike()`, `notIlike()`
376
+ - `gt`, `gte`, `lt`, `lte` → `gt()`, `gte()`, `lt()`, `lte()`
377
+ - `set`, `notSet` → `isNotNull()`, `isNull()`
378
+ - `inDateRange` → `and(gte(), lte())`
379
+
380
+ ## Documentation
381
+
382
+ Coming soon! 📚
383
+
384
+ ## Examples
385
+
386
+ Coming soon! Check the test files for usage patterns in the meantime.
387
+
388
+ ## Contributing
389
+
390
+ We welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md).
391
+
392
+ ## Roadmap
393
+
394
+ - 🔄 **Express Adapter** - Express.js integration
395
+ - 🔄 **Fastify Adapter** - Fastify integration
396
+ - 🔄 **Next.js Adapter** - Next.js API routes
397
+ - 🔄 **Pre-aggregations** - Materialized view support
398
+ - 🔄 **Real-time Updates** - WebSocket support
399
+ - 🔄 **Query Caching** - Redis integration
400
+
401
+ ## License
402
+
403
+ MIT © [Clifton Cunningham](https://github.com/cliftonc)
404
+
405
+ ---
406
+
407
+ **Built with ❤️ for the Drizzle ORM community**
@@ -0,0 +1,49 @@
1
+ import { Hono } from 'hono';
2
+ import { SemanticLayerCompiler, SemanticQuery, SecurityContext, DatabaseExecutor, DrizzleDatabase } from '../../server';
3
+ export interface HonoAdapterOptions<TSchema extends Record<string, any> = Record<string, any>> {
4
+ /**
5
+ * The semantic layer instance to use
6
+ */
7
+ semanticLayer: SemanticLayerCompiler<TSchema>;
8
+ /**
9
+ * Drizzle database instance (REQUIRED)
10
+ * This is the core of drizzle-cube - Drizzle ORM integration
11
+ */
12
+ drizzle: DrizzleDatabase<TSchema>;
13
+ /**
14
+ * Database schema for type inference (RECOMMENDED)
15
+ * Provides full type safety for cube definitions
16
+ */
17
+ schema?: TSchema;
18
+ /**
19
+ * Function to extract security context from Hono context
20
+ * This is where you provide your app-specific context extraction logic
21
+ */
22
+ getSecurityContext: (c: any) => SecurityContext | Promise<SecurityContext>;
23
+ /**
24
+ * CORS configuration (optional)
25
+ */
26
+ cors?: {
27
+ origin?: string | string[] | ((origin: string, c: any) => string | null | undefined);
28
+ allowMethods?: string[];
29
+ allowHeaders?: string[];
30
+ credentials?: boolean;
31
+ };
32
+ /**
33
+ * API base path (default: '/cubejs-api/v1')
34
+ */
35
+ basePath?: string;
36
+ }
37
+ /**
38
+ * Create Hono routes for Cube.js-compatible API
39
+ */
40
+ export declare function createCubeRoutes<TSchema extends Record<string, any> = Record<string, any>>(options: HonoAdapterOptions<TSchema>): Hono<import('hono/types').BlankEnv, import('hono/types').BlankSchema, "/">;
41
+ /**
42
+ * Convenience function to create routes and mount them on an existing Hono app
43
+ */
44
+ 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, "/">;
45
+ /**
46
+ * Create a complete Hono app with Cube.js routes
47
+ */
48
+ export declare function createCubeApp<TSchema extends Record<string, any> = Record<string, any>>(options: HonoAdapterOptions<TSchema>): Hono<import('hono/types').BlankEnv, import('hono/types').BlankSchema, "/">;
49
+ export type { SecurityContext, DatabaseExecutor, SemanticQuery, DrizzleDatabase };
@@ -1,5 +1,172 @@
1
- function o() {
1
+ import { Hono as w } from "hono";
2
+ var j = (f) => {
3
+ const u = {
4
+ ...{
5
+ origin: "*",
6
+ allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"],
7
+ allowHeaders: [],
8
+ exposeHeaders: []
9
+ },
10
+ ...f
11
+ }, q = /* @__PURE__ */ ((a) => typeof a == "string" ? a === "*" ? () => a : (t) => a === t ? t : null : typeof a == "function" ? a : (t) => a.includes(t) ? t : null)(u.origin), m = ((a) => typeof a == "function" ? a : Array.isArray(a) ? () => a : () => [])(u.allowMethods);
12
+ return async function(t, d) {
13
+ var i;
14
+ function e(r, n) {
15
+ t.res.headers.set(r, n);
16
+ }
17
+ const o = q(t.req.header("origin") || "", t);
18
+ if (o && e("Access-Control-Allow-Origin", o), u.origin !== "*") {
19
+ const r = t.req.header("Vary");
20
+ r ? e("Vary", r) : e("Vary", "Origin");
21
+ }
22
+ if (u.credentials && e("Access-Control-Allow-Credentials", "true"), (i = u.exposeHeaders) != null && i.length && e("Access-Control-Expose-Headers", u.exposeHeaders.join(",")), t.req.method === "OPTIONS") {
23
+ u.maxAge != null && e("Access-Control-Max-Age", u.maxAge.toString());
24
+ const r = m(t.req.header("origin") || "", t);
25
+ r.length && e("Access-Control-Allow-Methods", r.join(","));
26
+ let n = u.allowHeaders;
27
+ if (!(n != null && n.length)) {
28
+ const s = t.req.header("Access-Control-Request-Headers");
29
+ s && (n = s.split(/\s*,\s*/));
30
+ }
31
+ return n != null && n.length && (e("Access-Control-Allow-Headers", n.join(",")), t.res.headers.append("Vary", "Access-Control-Request-Headers")), t.res.headers.delete("Content-Length"), t.res.headers.delete("Content-Type"), new Response(null, {
32
+ headers: t.res.headers,
33
+ status: 204,
34
+ statusText: "No Content"
35
+ });
36
+ }
37
+ await d();
38
+ };
39
+ };
40
+ function C(f) {
41
+ const {
42
+ semanticLayer: l,
43
+ drizzle: u,
44
+ schema: q,
45
+ getSecurityContext: m,
46
+ cors: a,
47
+ basePath: t = "/cubejs-api/v1"
48
+ } = f, d = new w();
49
+ return a && d.use("/*", j(a)), l.hasExecutor() || l.setDrizzle(u, q), d.post(`${t}/load`, async (e) => {
50
+ var o, i;
51
+ try {
52
+ const r = await e.req.json(), n = await m(e);
53
+ if (!((o = r.measures) != null && o.length) && !((i = r.dimensions) != null && i.length))
54
+ return e.json({
55
+ error: "Query must specify at least one measure or dimension"
56
+ }, 400);
57
+ const s = await l.executeMultiCubeQuery(r, n);
58
+ return e.json({
59
+ data: s.data,
60
+ annotation: s.annotation,
61
+ query: r,
62
+ slowQuery: !1
63
+ });
64
+ } catch (r) {
65
+ return console.error("Query execution error:", r), e.json({
66
+ error: r instanceof Error ? r.message : "Query execution failed"
67
+ }, 500);
68
+ }
69
+ }), d.get(`${t}/load`, async (e) => {
70
+ var o, i;
71
+ try {
72
+ const r = e.req.query("query");
73
+ if (!r)
74
+ return e.json({
75
+ error: "Query parameter is required"
76
+ }, 400);
77
+ const n = JSON.parse(r), s = await m(e);
78
+ if (!((o = n.measures) != null && o.length) && !((i = n.dimensions) != null && i.length))
79
+ return e.json({
80
+ error: "Query must specify at least one measure or dimension"
81
+ }, 400);
82
+ const c = await l.executeMultiCubeQuery(n, s);
83
+ return e.json({
84
+ data: c.data,
85
+ annotation: c.annotation,
86
+ query: n,
87
+ slowQuery: !1
88
+ });
89
+ } catch (r) {
90
+ return console.error("Query execution error:", r), e.json({
91
+ error: r instanceof Error ? r.message : "Query execution failed"
92
+ }, 500);
93
+ }
94
+ }), d.get(`${t}/meta`, async (e) => {
95
+ try {
96
+ const o = l.getMetadata();
97
+ return e.json({
98
+ cubes: o
99
+ });
100
+ } catch (o) {
101
+ return console.error("Metadata error:", o), e.json({
102
+ error: o instanceof Error ? o.message : "Failed to fetch metadata"
103
+ }, 500);
104
+ }
105
+ }), d.post(`${t}/sql`, async (e) => {
106
+ var o, i, r, n;
107
+ try {
108
+ const s = await e.req.json(), c = await m(e);
109
+ if (!((o = s.measures) != null && o.length) && !((i = s.dimensions) != null && i.length))
110
+ return e.json({
111
+ error: "Query must specify at least one measure or dimension"
112
+ }, 400);
113
+ const y = ((r = s.measures) == null ? void 0 : r[0]) || ((n = s.dimensions) == null ? void 0 : n[0]);
114
+ if (!y)
115
+ return e.json({
116
+ error: "No measures or dimensions specified"
117
+ }, 400);
118
+ const g = y.split(".")[0], h = await l.generateSQL(g, s, c);
119
+ return e.json({
120
+ sql: h.sql,
121
+ params: h.params || [],
122
+ query: s
123
+ });
124
+ } catch (s) {
125
+ return console.error("SQL generation error:", s), e.json({
126
+ error: s instanceof Error ? s.message : "SQL generation failed"
127
+ }, 500);
128
+ }
129
+ }), d.get(`${t}/sql`, async (e) => {
130
+ var o, i, r, n;
131
+ try {
132
+ const s = e.req.query("query");
133
+ if (!s)
134
+ return e.json({
135
+ error: "Query parameter is required"
136
+ }, 400);
137
+ const c = JSON.parse(s), y = await m(e);
138
+ if (!((o = c.measures) != null && o.length) && !((i = c.dimensions) != null && i.length))
139
+ return e.json({
140
+ error: "Query must specify at least one measure or dimension"
141
+ }, 400);
142
+ const g = ((r = c.measures) == null ? void 0 : r[0]) || ((n = c.dimensions) == null ? void 0 : n[0]);
143
+ if (!g)
144
+ return e.json({
145
+ error: "No measures or dimensions specified"
146
+ }, 400);
147
+ const h = g.split(".")[0], p = await l.generateSQL(h, c, y);
148
+ return e.json({
149
+ sql: p.sql,
150
+ params: p.params || [],
151
+ query: c
152
+ });
153
+ } catch (s) {
154
+ return console.error("SQL generation error:", s), e.json({
155
+ error: s instanceof Error ? s.message : "SQL generation failed"
156
+ }, 500);
157
+ }
158
+ }), d;
159
+ }
160
+ function x(f, l) {
161
+ const u = C(l);
162
+ return f.route("/", u), f;
163
+ }
164
+ function A(f) {
165
+ const l = new w();
166
+ return x(l, f);
2
167
  }
3
168
  export {
4
- o as createHonoRoutes
169
+ A as createCubeApp,
170
+ C as createCubeRoutes,
171
+ x as mountCubeRoutes
5
172
  };
@@ -0,0 +1,41 @@
1
+ import { SemanticLayerCompiler, SecurityContext, DatabaseExecutor } from '../server';
2
+ /**
3
+ * Base adapter configuration
4
+ */
5
+ export interface BaseAdapterOptions {
6
+ semanticLayer: SemanticLayerCompiler;
7
+ databaseExecutor?: DatabaseExecutor;
8
+ basePath?: string;
9
+ }
10
+ /**
11
+ * Framework-specific context extractor
12
+ * Each framework adapter will provide their own context type
13
+ */
14
+ export interface ContextExtractor<TContext = any> {
15
+ (context: TContext): SecurityContext | Promise<SecurityContext>;
16
+ }
17
+ /**
18
+ * Standard CORS configuration
19
+ */
20
+ export interface CorsConfig {
21
+ origin?: string | string[] | ((origin: string) => boolean);
22
+ allowMethods?: string[];
23
+ allowHeaders?: string[];
24
+ credentials?: boolean;
25
+ }
26
+ /**
27
+ * Standard adapter response format
28
+ */
29
+ export interface AdapterResponse {
30
+ data?: any;
31
+ error?: string;
32
+ status?: number;
33
+ }
34
+ /**
35
+ * Future adapter interface (for Express, Fastify, etc.)
36
+ */
37
+ export interface AdapterFactory<TOptions extends BaseAdapterOptions, TApp = any> {
38
+ createRoutes(options: TOptions): TApp;
39
+ mountRoutes?(app: TApp, options: TOptions): TApp;
40
+ createApp?(options: TOptions): TApp;
41
+ }