@@ -7,7 +7,9 @@ import packageJson from '../../package.json'
77// bun specific driver + shared classes
88import { SQLiteCloudBunConnection } from './connection-bun'
99import { SQLiteCloudRowset , SQLiteCloudError , validateConfiguration } from '../index'
10- import { type ApiRequest , type ApiResponse , type SqlApiRequest , DEFAULT_PORT_HTTP , DEFAULT_PORT_SOCKET } from './shared'
10+ import { type ApiRequest , type ApiResponse , type SqlApiRequest , DEFAULT_PORT_HTTP , DEFAULT_PORT_SOCKET , GatewayError } from './shared'
11+ import { VERBOSE , connectAsync , sendCommandsAsync , log , errorResponse } from './utilities'
12+ import { getServerInfo , getDatabases , getStats } from './api'
1113
1214// external modules
1315import { heapStats } from 'bun:jsc'
@@ -19,9 +21,8 @@ import http from 'http'
1921const SOCKET_PORT = parseInt ( process . env [ 'SOCKET_PORT' ] || DEFAULT_PORT_SOCKET . toString ( ) )
2022// port where http server will listen for connections
2123const HTTP_PORT = parseInt ( process . env [ 'HTTP_PORT' ] || DEFAULT_PORT_HTTP . toString ( ) )
22- // should we log verbose messages?
23- const VERBOSE = process . env [ 'VERBOSE' ] ?. toLowerCase ( ) === 'true'
24- console . debug ( `@sqlitecloud/gateway v${ packageJson . version } ` )
24+
25+ console . log ( `@sqlitecloud/gateway v${ packageJson . version } ` )
2526
2627//
2728// express
@@ -65,13 +66,45 @@ io.on('connection', socket => {
6566 // handlers
6667 //
6768
69+ async function getConnection ( ) : Promise < SQLiteCloudBunConnection > {
70+ if ( ! connection ) {
71+ const startTime = Date . now ( )
72+ log ( 'ws | connecting...' )
73+ connection = await connectAsync ( connectionString )
74+ log ( `ws | connected in ${ Date . now ( ) - startTime } ms` )
75+ }
76+ return connection
77+ }
78+
79+ async function callbackWithApiResponse (
80+ callback : ( response : ApiResponse ) => void ,
81+ func : ( connection : SQLiteCloudBunConnection , ...args : any [ ] ) => Promise < ApiResponse > ,
82+ ...args : any [ ]
83+ ) {
84+ try {
85+ const connection = await getConnection ( )
86+ const response = await func ( connection , ...args )
87+ callback ( response )
88+ } catch ( error ) {
89+ callback ( { error : { status : '500' , title : String ( error ) } } )
90+ }
91+ }
92+
6893 // received a sql query request from the client socket
69- socket . on ( 'v1/info' , ( _request : ApiRequest , callback : ( response : ApiResponse ) => void ) => {
94+ socket . on ( 'v1/info' , async ( _request : ApiRequest , callback : ( response : ApiResponse ) => void ) => {
7095 const serverInfo = getServerInfo ( )
7196 log ( `ws | info <- ${ JSON . stringify ( serverInfo ) } ` )
7297 return callback ( serverInfo )
7398 } )
7499
100+ socket . on ( 'v1/databases' , async ( _request : ApiRequest , callback : ( response : ApiResponse ) => void ) => {
101+ await callbackWithApiResponse ( callback , getDatabases )
102+ } )
103+
104+ socket . on ( 'v1/stats' , async ( _request : ApiRequest , callback : ( response : ApiResponse ) => void ) => {
105+ await callbackWithApiResponse ( callback , getStats )
106+ } )
107+
75108 // received a sql query request from the client socket
76109 socket . on ( 'v1/sql' , async ( request : SqlApiRequest , callback : ( response : ApiResponse ) => void ) => {
77110 if ( ! connectionString ) {
@@ -80,14 +113,8 @@ io.on('connection', socket => {
80113 }
81114
82115 try {
83- if ( ! connection ) {
84- const startTime = Date . now ( )
85- log ( 'ws | connecting...' )
86- connection = await connectAsync ( connectionString )
87- log ( `ws | connected in ${ Date . now ( ) - startTime } ms` )
88- }
89-
90116 log ( `ws | sql -> ${ JSON . stringify ( request ) } ` )
117+ const connection = await getConnection ( )
91118 const response = await queryAsync ( connection , request )
92119 log ( `ws | sql <- ${ JSON . stringify ( response ) } ` )
93120 return callback ( response )
@@ -121,31 +148,59 @@ app.get('/v1/info', (req, res) => {
121148 res . json ( getServerInfo ( ) )
122149} )
123150
124- app . post ( '/v1/sql' , ( req : express . Request , res : express . Response ) => {
125- void ( async ( ) => {
126- try {
127- log ( 'POST /v1/sql' )
128- const response = await handleHttpSqlRequest ( req , res )
129- res . json ( response )
130- } catch ( error ) {
131- log ( 'POST /v1/sql - error' , error )
132- res . status ( 400 ) . json ( { error : { status : '400' , title : 'Bad Request' , detail : error as string } } )
133- }
134- } )
151+ app . get ( '/v1/databases' , async ( request , response ) => {
152+ try {
153+ response . json ( await getDatabases ( await getRequestConnection ( request ) ) )
154+ } catch ( error ) {
155+ errorResponse ( response , 500 , 'Error' , error )
156+ }
157+ } )
158+
159+ app . get ( '/v1/stats' , async ( request , response ) => {
160+ try {
161+ response . json ( await getStats ( await getRequestConnection ( request ) ) )
162+ } catch ( error ) {
163+ errorResponse ( response , 500 , 'Error' , error )
164+ }
165+ } )
166+
167+ app . post ( '/v1/sql' , async ( req : express . Request , res : express . Response ) => {
168+ try {
169+ log ( 'POST /v1/sql' )
170+ const response = await handleHttpSqlRequest ( req , res )
171+ res . json ( response )
172+ } catch ( error ) {
173+ log ( 'POST /v1/sql - error' , error )
174+ res . status ( 400 ) . json ( { error : { status : '400' , title : 'Bad Request' , detail : error as string } } )
175+ }
135176} )
136177
137178//
138179// utilities
139180//
140181
141- /** Handle a stateless sql query request */
142- async function handleHttpSqlRequest ( request : express . Request , response : express . Response ) {
182+ /** Extract and return bearer token from request authorization headers */
183+ function getRequestToken ( request : express . Request ) : string | null {
184+ const authorization = request . headers [ 'authorization' ] as string
185+ // console.debug(`getBearerToken - ${authorization}`, request.headers)
186+ if ( authorization && authorization . startsWith ( 'Bearer ' ) ) {
187+ return authorization . substring ( 7 )
188+ }
189+ return null
190+ }
191+
192+ /** Returns database connection associated with express request credentials */
193+ async function getRequestConnection ( request : express . Request ) : Promise < SQLiteCloudBunConnection > {
143194 // bearer token is required to connect to sqlitecloud
144- const connectionString = getBearerToken ( request )
195+ const connectionString = getRequestToken ( request )
145196 if ( ! connectionString ) {
146- return errorResponse ( response , 401 , 'Unauthorized' )
197+ throw new GatewayError ( 'Unauthorized' , { status : 401 } )
147198 }
199+ return await connectAsync ( connectionString )
200+ }
148201
202+ /** Handle a stateless sql query request */
203+ async function handleHttpSqlRequest ( request : express . Request , response : express . Response ) {
149204 // ?sql= or json payload with sql property is required
150205 let apiRequest : SqlApiRequest
151206 try {
@@ -161,88 +216,23 @@ async function handleHttpSqlRequest(request: express.Request, response: express.
161216 return errorResponse ( response , 400 , 'Bad Request' , 'Missing ?sql= query or json payload' )
162217 }
163218
164- let connection
219+ let connection = null
165220 try {
166221 // request is stateless so we will connect and disconnect for each request
167222 log ( `http | sql -> ${ JSON . stringify ( apiRequest ) } ` )
168- connection = await connectAsync ( connectionString )
223+ connection = await getRequestConnection ( request )
169224 const apiResponse = await queryAsync ( connection , apiRequest )
170225 log ( `http | sql <- ${ JSON . stringify ( apiResponse ) } ` )
171226 response . json ( apiResponse )
172227 } catch ( error ) {
173228 errorResponse ( response , 400 , 'Bad Request' , ( error as Error ) . toString ( ) )
174229 } finally {
175- connection ?. close ( )
176- }
177- }
178-
179- /** Server info for /v1/info endpoints */
180- function getServerInfo ( ) {
181- // eslint-disable-next-line @typescript-eslint/no-unused-vars
182- const { objectTypeCounts, protectedObjectTypeCounts, ...memory } = heapStats ( )
183- return {
184- data : {
185- name : '@sqlitecloud/gateway' ,
186- version : packageJson . version ,
187- started : appStartedOn . toISOString ( ) ,
188- uptime : `${ Math . floor ( process . uptime ( ) / 3600 ) } h:${ Math . floor ( ( process . uptime ( ) % 3600 ) / 60 ) } m:${ Math . floor ( process . uptime ( ) % 60 ) } s` ,
189- bun : {
190- version : Bun . version ,
191- path : Bun . which ( 'bun' ) ,
192- main : Bun . main
193- } ,
194- memory,
195- cpuUsage : process . cpuUsage ( )
230+ if ( connection ) {
231+ connection . close ( )
196232 }
197233 }
198234}
199235
200- /** Extract and return bearer token from request authorization headers */
201- function getBearerToken ( request : express . Request ) : string | null {
202- const authorization = request . headers [ 'authorization' ] as string
203- // console.debug(`getBearerToken - ${authorization}`, request.headers)
204- if ( authorization && authorization . startsWith ( 'Bearer ' ) ) {
205- return authorization . substring ( 7 )
206- }
207- return null
208- }
209-
210- /** Returns a json api compatibile error response */
211- function errorResponse ( response : express . Response , status : number , statusText : string , detail ?: string ) {
212- response . status ( status ) . json ( { error : { status : status . toString ( ) , title : statusText , detail } } )
213- }
214-
215- /** Connects to given database asynchronously */
216- async function connectAsync ( connectionString : string ) : Promise < SQLiteCloudBunConnection > {
217- return await new Promise ( ( resolve , reject ) => {
218- const config = validateConfiguration ( { connectionString } )
219- const connection = new SQLiteCloudBunConnection ( config , ( error : Error | null ) => {
220- if ( error ) {
221- log ( 'connectAsync | error' , error )
222- reject ( error )
223- } else {
224- resolve ( connection )
225- }
226- } )
227- } )
228- }
229-
230- /** Sends given sql commands asynchronously */
231- async function sendCommandsAsync ( connection : SQLiteCloudBunConnection , sql : string ) : Promise < unknown > {
232- return await new Promise ( ( resolve , reject ) => {
233- connection . sendCommands ( sql , ( error : Error | null , results ) => {
234- // Explicitly type the 'error' parameter as 'Error'
235- if ( error ) {
236- log ( 'sendCommandsAsync | error' , error )
237- reject ( error )
238- } else {
239- // console.debug(JSON.stringify(results).substring(0, 140) + '...')
240- resolve ( results )
241- }
242- } )
243- } )
244- }
245-
246236/** Runs query on given connection and returns response payload */
247237async function queryAsync ( connection : SQLiteCloudBunConnection , apiRequest : SqlApiRequest ) : Promise < ApiResponse > {
248238 let result : unknown = 'OK'
@@ -323,10 +313,3 @@ function generateMetadata(sql: string, result: any): ApiResponse {
323313
324314 return { data : result }
325315}
326-
327- /** Log only in verbose mode */
328- function log ( ...args : unknown [ ] ) {
329- if ( VERBOSE ) {
330- console . debug ( ...args )
331- }
332- }
0 commit comments