@@ -40,16 +40,13 @@ type HonoContext = {
4040 }
4141}
4242
43- const app = new Hono < HonoContext > ( )
44-
45- export type StarbaseApp = typeof app
46- export type StarbaseContext = Context < HonoContext >
47-
4843export class StarbaseDB {
4944 private dataSource : DataSource
5045 private config : StarbaseDBConfiguration
5146 private liteREST : LiteREST
5247 private plugins : StarbasePlugin [ ]
48+ private initialized : boolean = false
49+ private app : StarbaseApp
5350
5451 constructor ( options : {
5552 dataSource : DataSource
@@ -60,6 +57,7 @@ export class StarbaseDB {
6057 this . config = options . config
6158 this . liteREST = new LiteREST ( this . dataSource , this . config )
6259 this . plugins = options . plugins || [ ]
60+ this . app = new Hono < HonoContext > ( )
6361
6462 if (
6563 this . dataSource . source === 'external' &&
@@ -69,62 +67,11 @@ export class StarbaseDB {
6967 }
7068 }
7169
72- /**
73- * Middleware to check if the request is coming from an internal source.
74- */
75- private get isInternalSource ( ) {
76- return createMiddleware ( async ( _ , next ) => {
77- if ( this . dataSource . source !== 'internal' ) {
78- return createResponse (
79- undefined ,
80- 'Function is only available for internal data source.' ,
81- 400
82- )
83- }
84-
85- return next ( )
86- } )
87- }
88-
89- /**
90- * Validator middleware to check if the request path has a valid :tableName parameter.
91- */
92- private get hasTableName ( ) {
93- return validator ( 'param' , ( params ) => {
94- const tableName = params [ 'tableName' ] . trim ( )
95-
96- if ( ! tableName ) {
97- return createResponse ( undefined , 'Table name is required' , 400 )
98- }
99-
100- return { tableName }
101- } )
102- }
103-
104- /**
105- * Helper function to get a feature flag from the configuration.
106- * @param key The feature key to get.
107- * @param defaultValue The default value to return if the feature is not defined.
108- * @returns
109- */
110- private getFeature (
111- key : keyof NonNullable < StarbaseDBConfiguration [ 'features' ] > ,
112- defaultValue = true
113- ) : boolean {
114- return this . config . features ?. [ key ] ?? ! ! defaultValue
115- }
70+ private async initialize ( ) {
71+ if ( this . initialized ) return
11672
117- /**
118- * Main handler function for the StarbaseDB.
119- * @param request Request instance from the fetch event.
120- * @returns Promise<Response>
121- */
122- public async handle (
123- request : Request ,
124- ctx : ExecutionContext
125- ) : Promise < Response > {
126- // Add context to the request
127- app . use ( '*' , async ( c , next ) => {
73+ // Set up middleware first
74+ this . app . use ( '*' , async ( c , next ) => {
12875 c . set ( 'config' , this . config )
12976 c . set ( 'dataSource' , this . dataSource )
13077 c . set ( 'operations' , {
@@ -134,48 +81,30 @@ export class StarbaseDB {
13481 return next ( )
13582 } )
13683
137- // Non-blocking operation to remove expired cache entries from our DO
138- ctx . waitUntil ( this . expireCache ( ) )
139-
140- // General 404 not found handler
141- app . notFound ( ( ) => {
142- return createResponse ( undefined , 'Not found' , 404 )
143- } )
144-
145- // Thrown error handler
146- app . onError ( ( error ) => {
147- return createResponse (
148- undefined ,
149- error ?. message || 'An unexpected error occurred.' ,
150- 500
151- )
152- } )
153-
84+ // Initialize plugins
15485 const registry = new StarbasePluginRegistry ( {
155- app,
86+ app : this . app ,
15687 plugins : this . plugins ,
15788 } )
158-
15989 await registry . init ( )
16090
161- // CORS preflight handler.
162- app . options ( '*' , ( ) => corsPreflight ( ) )
163-
164- app . post ( '/query/raw' , async ( c ) => this . queryRoute ( c . req . raw , true ) )
165- app . post ( '/query' , async ( c ) => this . queryRoute ( c . req . raw , false ) )
91+ this . app . post ( '/query/raw' , async ( c ) =>
92+ this . queryRoute ( c . req . raw , true )
93+ )
94+ this . app . post ( '/query' , async ( c ) => this . queryRoute ( c . req . raw , false ) )
16695
16796 if ( this . getFeature ( 'rest' ) ) {
168- app . all ( '/rest/*' , async ( c ) => {
97+ this . app . all ( '/rest/*' , async ( c ) => {
16998 return this . liteREST . handleRequest ( c . req . raw )
17099 } )
171100 }
172101
173102 if ( this . getFeature ( 'export' ) ) {
174- app . get ( '/export/dump' , this . isInternalSource , async ( ) => {
103+ this . app . get ( '/export/dump' , this . isInternalSource , async ( ) => {
175104 return dumpDatabaseRoute ( this . dataSource , this . config )
176105 } )
177106
178- app . get (
107+ this . app . get (
179108 '/export/json/:tableName' ,
180109 this . isInternalSource ,
181110 this . hasTableName ,
@@ -189,7 +118,7 @@ export class StarbaseDB {
189118 }
190119 )
191120
192- app . get (
121+ this . app . get (
193122 '/export/csv/:tableName' ,
194123 this . isInternalSource ,
195124 this . hasTableName ,
@@ -205,44 +134,151 @@ export class StarbaseDB {
205134 }
206135
207136 if ( this . getFeature ( 'import' ) ) {
208- app . post ( '/import/dump' , this . isInternalSource , async ( c ) => {
137+ this . app . post ( '/import/dump' , this . isInternalSource , async ( c ) => {
209138 return importDumpRoute ( c . req . raw , this . dataSource , this . config )
210139 } )
211140
212- app . post (
141+ this . app . post (
213142 '/import/json/:tableName' ,
214143 this . isInternalSource ,
215144 this . hasTableName ,
216145 async ( c ) => {
217146 const tableName = c . req . valid ( 'param' ) . tableName
218147 return importTableFromJsonRoute (
219148 tableName ,
220- request ,
149+ c . req . raw ,
221150 this . dataSource ,
222151 this . config
223152 )
224153 }
225154 )
226155
227- app . post (
156+ this . app . post (
228157 '/import/csv/:tableName' ,
229158 this . isInternalSource ,
230159 this . hasTableName ,
231160 async ( c ) => {
232161 const tableName = c . req . valid ( 'param' ) . tableName
233162 return importTableFromCsvRoute (
234163 tableName ,
235- request ,
164+ c . req . raw ,
236165 this . dataSource ,
237166 this . config
238167 )
239168 }
240169 )
241170 }
242171
243- app . all ( '/api/*' , async ( c ) => handleApiRequest ( c . req . raw ) )
172+ this . app . all ( '/api/*' , async ( c ) => handleApiRequest ( c . req . raw ) )
244173
245- return app . fetch ( request )
174+ // Set up error handlers
175+ this . app . notFound ( ( ) => {
176+ return createResponse ( undefined , 'Not found' , 404 )
177+ } )
178+
179+ this . app . onError ( ( error ) => {
180+ return createResponse (
181+ undefined ,
182+ error ?. message || 'An unexpected error occurred.' ,
183+ 500
184+ )
185+ } )
186+
187+ this . initialized = true
188+ }
189+
190+ public async handlePreAuth (
191+ request : Request ,
192+ ctx : ExecutionContext
193+ ) : Promise < Response | undefined > {
194+ // Initialize everything once
195+ await this . initialize ( )
196+
197+ const authlessPlugin = this . plugins . find ( ( plugin : StarbasePlugin ) => {
198+ if ( ! plugin . opts . requiresAuth && request . url && plugin . pathPrefix ) {
199+ // Extract the path from the full URL
200+ const urlPath = new URL ( request . url ) . pathname
201+
202+ // Convert plugin path pattern to regex
203+ const pathPattern = plugin . pathPrefix
204+ . replace ( / : [ ^ / ] + / g, '[^/]+' ) // Replace :param with regex pattern
205+ . replace ( / \* / g, '.*' ) // Replace * with wildcard pattern
206+
207+ const regex = new RegExp ( `^${ pathPattern } $` )
208+ return regex . test ( urlPath )
209+ }
210+
211+ return false
212+ } )
213+
214+ if ( authlessPlugin ) {
215+ return this . app . fetch ( request )
216+ }
217+
218+ return undefined
219+ }
220+
221+ public async handle (
222+ request : Request ,
223+ ctx : ExecutionContext
224+ ) : Promise < Response > {
225+ // Initialize everything once
226+ await this . initialize ( )
227+
228+ // Non-blocking operation to remove expired cache entries from our DO
229+ ctx . waitUntil ( this . expireCache ( ) )
230+
231+ // CORS preflight handler
232+ if ( request . method === 'OPTIONS' ) {
233+ return corsPreflight ( )
234+ }
235+
236+ return this . app . fetch ( request )
237+ }
238+
239+ /**
240+ * Middleware to check if the request is coming from an internal source.
241+ */
242+ private get isInternalSource ( ) {
243+ return createMiddleware ( async ( _ , next ) => {
244+ if ( this . dataSource . source !== 'internal' ) {
245+ return createResponse (
246+ undefined ,
247+ 'Function is only available for internal data source.' ,
248+ 400
249+ )
250+ }
251+
252+ return next ( )
253+ } )
254+ }
255+
256+ /**
257+ * Validator middleware to check if the request path has a valid :tableName parameter.
258+ */
259+ private get hasTableName ( ) {
260+ return validator ( 'param' , ( params ) => {
261+ const tableName = params [ 'tableName' ] . trim ( )
262+
263+ if ( ! tableName ) {
264+ return createResponse ( undefined , 'Table name is required' , 400 )
265+ }
266+
267+ return { tableName }
268+ } )
269+ }
270+
271+ /**
272+ * Helper function to get a feature flag from the configuration.
273+ * @param key The feature key to get.
274+ * @param defaultValue The default value to return if the feature is not defined.
275+ * @returns
276+ */
277+ private getFeature (
278+ key : keyof NonNullable < StarbaseDBConfiguration [ 'features' ] > ,
279+ defaultValue = true
280+ ) : boolean {
281+ return this . config . features ?. [ key ] ?? ! ! defaultValue
246282 }
247283
248284 async queryRoute ( request : Request , isRaw : boolean ) : Promise < Response > {
@@ -339,3 +375,6 @@ export class StarbaseDB {
339375 }
340376 }
341377}
378+
379+ export type StarbaseApp = Hono < HonoContext >
380+ export type StarbaseContext = Context < HonoContext >
0 commit comments