11import { Hono } from 'hono' ;
22import { cors } from 'hono/cors' ;
3- import { type ObjectKernel , HttpDispatcher } from '@objectstack/runtime' ;
3+ import { type ObjectKernel , HttpDispatcher , HttpDispatcherResult } from '@objectstack/runtime' ;
44
55export interface ObjectStackHonoOptions {
66 kernel : ObjectKernel ;
@@ -14,204 +14,111 @@ export interface ObjectStackHonoOptions {
1414export function createHonoApp ( options : ObjectStackHonoOptions ) {
1515 const app = new Hono ( ) ;
1616 const { prefix = '/api' } = options ;
17- const kernel = options . kernel as any ;
1817 const dispatcher = new HttpDispatcher ( options . kernel ) ;
1918
2019 app . use ( '*' , cors ( ) ) ;
2120
22- // --- Helper for Success Response ---
23- const success = ( data : any , meta ?: any ) => ( {
24- success : true ,
25- data,
26- meta,
27- } ) ;
28-
29- // --- Helper for Error Response ---
30- const errorHandler = async ( c : any , fn : ( ) => Promise < any > ) => {
31- try {
32- return await fn ( ) ;
33- } catch ( err : any ) {
34- return c . json ( {
35- success : false ,
36- error : {
37- code : err . statusCode || 500 ,
38- message : err . message || 'Internal Server Error' ,
39- details : err . details ,
40- } ,
41- } , err . statusCode || 500 ) ;
42- }
43- } ;
21+ // --- Helper for Response Normalization ---
22+ const normalizeResponse = ( c : any , result : HttpDispatcherResult ) => {
23+ if ( result . handled ) {
24+ if ( result . response ) {
25+ return c . json ( result . response . body , result . response . status as any , result . response . headers ) ;
26+ }
27+ if ( result . result ) {
28+ const res = result . result ;
29+ // Redirect
30+ if ( res . type === 'redirect' && res . url ) {
31+ return c . redirect ( res . url ) ;
32+ }
33+ // Stream
34+ if ( res . type === 'stream' && res . stream ) {
35+ return c . body ( res . stream , 200 , res . headers ) ;
36+ }
37+
38+ // Hono handles standard Response objects
39+ return res ;
40+ }
41+ }
42+ return c . json ( { success : false , error : { message : 'Not Found' , code : 404 } } , 404 ) ;
43+ }
4444
4545 // --- 0. Discovery Endpoint ---
4646 app . get ( prefix , ( c ) => {
4747 return c . json ( dispatcher . getDiscoveryInfo ( prefix ) ) ;
4848 } ) ;
4949
50- // --- 1. Auth (Generic Auth Handler) ---
50+ // --- 1. Auth ---
5151 app . all ( `${ prefix } /auth/*` , async ( c ) => {
52- // subpath from /api/auth/login -> login
53- const path = c . req . path . substring ( c . req . path . indexOf ( '/auth/' ) + 6 ) ;
54-
5552 try {
56- const result = await dispatcher . handleAuth ( path , c . req . method , await c . req . parseBody ( ) . catch ( ( ) => ( { } ) ) , { request : c . req . raw } ) ;
53+ // subpath from /api/auth/login -> login
54+ const path = c . req . path . substring ( c . req . path . indexOf ( '/auth/' ) + 6 ) ;
55+ const body = await c . req . parseBody ( ) . catch ( ( ) => ( { } ) ) ;
5756
58- if ( result . handled ) {
59- if ( result . response ) {
60- return c . json ( result . response . body , result . response . status as any , result . response . headers ) ;
61- }
62- if ( result . result ) {
63- // If handler returns a response object
64- return result . result ;
65- }
66- }
67- return c . json ( { success : false , error : { message : 'Auth provider not configured' , code : 404 } } , 404 ) ;
57+ const result = await dispatcher . handleAuth ( path , c . req . method , body , { request : c . req . raw } ) ;
58+ return normalizeResponse ( c , result ) ;
6859 } catch ( err : any ) {
6960 return c . json ( { success : false , error : { message : err . message , code : err . statusCode || 500 } } , err . statusCode || 500 ) ;
7061 }
7162 } ) ;
7263
7364 // --- 2. GraphQL ---
7465 app . post ( `${ prefix } /graphql` , async ( c ) => {
75- return errorHandler ( c , async ( ) => {
66+ try {
7667 const body = await c . req . json ( ) ;
7768 const result = await dispatcher . handleGraphQL ( body , { request : c . req . raw } ) ;
7869 return c . json ( result ) ;
79- } ) ;
80- } ) ;
81-
82- // --- 2. Metadata Endpoints ---
83-
84- // List All Objects
85- app . get ( `${ prefix } /metadata` , async ( c ) => {
86- return errorHandler ( c , async ( ) => {
87- const data = await kernel . broker . call ( 'metadata.objects' , { } , { request : c . req . raw } ) ;
88- return c . json ( success ( data ) ) ;
89- } ) ;
70+ } catch ( err : any ) {
71+ return c . json ( { success : false , error : { message : err . message , code : err . statusCode || 500 } } , err . statusCode || 500 ) ;
72+ }
9073 } ) ;
9174
92- // Get Object Metadata
93- app . get ( `${ prefix } /metadata/:objectName` , async ( c ) => {
94- return errorHandler ( c , async ( ) => {
95- const { objectName } = c . req . param ( ) ;
96- const data = await kernel . broker . call ( 'metadata.getObject' , { objectName } , { request : c . req . raw } ) ;
97- return c . json ( success ( data ) ) ;
98- } ) ;
75+ // --- 3. Metadata Endpoints ---
76+ app . all ( `${ prefix } /metadata*` , async ( c ) => {
77+ try {
78+ const path = c . req . path . substring ( c . req . path . indexOf ( '/metadata' ) + 9 ) ;
79+ const result = await dispatcher . handleMetadata ( path , { request : c . req . raw } ) ;
80+ return normalizeResponse ( c , result ) ;
81+ } catch ( err : any ) {
82+ return c . json ( { success : false , error : { message : err . message , code : err . statusCode || 500 } } , err . statusCode || 500 ) ;
83+ }
9984 } ) ;
10085
101- // --- 3. Data Endpoints ---
102-
103- // List Records (Standard REST)
104- app . get ( `${ prefix } /data/:objectName` , async ( c ) => {
105- return errorHandler ( c , async ( ) => {
106- const { objectName } = c . req . param ( ) ;
86+ // --- 4. Data Endpoints ---
87+ app . all ( `${ prefix } /data*` , async ( c ) => {
88+ try {
89+ const path = c . req . path . substring ( c . req . path . indexOf ( '/data' ) + 5 ) ;
90+ const method = c . req . method ;
91+
92+ let body = { } ;
93+ if ( method === 'POST' || method === 'PATCH' ) {
94+ body = await c . req . json ( ) . catch ( ( ) => ( { } ) ) ;
95+ }
10796 const query = c . req . query ( ) ;
108- // Basic support: pass query params as filter
109- // In a real implementation, we might parse OData or JSON filters from query params
110- const result = await kernel . broker . call ( 'data.query' , { object : objectName , filters : query } , { request : c . req . raw } ) ;
111- return c . json ( success ( result . data , { count : result . count } ) ) ;
112- } ) ;
113- } ) ;
11497
115- // Query Records (POST with JSON body)
116- app . post ( `${ prefix } /data/:objectName/query` , async ( c ) => {
117- return errorHandler ( c , async ( ) => {
118- const { objectName } = c . req . param ( ) ;
119- const body = await c . req . json ( ) ;
120- const result = await kernel . broker . call ( 'data.query' , { object : objectName , ...body } , { request : c . req . raw } ) ;
121- return c . json ( success ( result . data , { count : result . count , limit : body . limit , skip : body . skip } ) ) ;
122- } ) ;
123- } ) ;
124-
125- // Get Single Record
126- app . get ( `${ prefix } /data/:objectName/:id` , async ( c ) => {
127- return errorHandler ( c , async ( ) => {
128- const { objectName, id } = c . req . param ( ) ;
129- const query = c . req . query ( ) ;
130- const data = await kernel . broker . call ( 'data.get' , { object : objectName , id, ...query } , { request : c . req . raw } ) ;
131- return c . json ( success ( data ) ) ;
132- } ) ;
133- } ) ;
134-
135- // Create Record
136- app . post ( `${ prefix } /data/:objectName` , async ( c ) => {
137- return errorHandler ( c , async ( ) => {
138- const { objectName } = c . req . param ( ) ;
139- const body = await c . req . json ( ) ;
140- const data = await kernel . broker . call ( 'data.create' , { object : objectName , data : body } , { request : c . req . raw } ) ;
141- return c . json ( success ( data ) , 201 ) ;
142- } ) ;
143- } ) ;
144-
145- // Update Record
146- app . patch ( `${ prefix } /data/:objectName/:id` , async ( c ) => {
147- return errorHandler ( c , async ( ) => {
148- const { objectName, id } = c . req . param ( ) ;
149- const body = await c . req . json ( ) ;
150- const data = await kernel . broker . call ( 'data.update' , { object : objectName , id, data : body } , { request : c . req . raw } ) ;
151- return c . json ( success ( data ) ) ;
152- } ) ;
153- } ) ;
154-
155- // Delete Record
156- app . delete ( `${ prefix } /data/:objectName/:id` , async ( c ) => {
157- return errorHandler ( c , async ( ) => {
158- const { objectName, id } = c . req . param ( ) ;
159- await kernel . broker . call ( 'data.delete' , { object : objectName , id } , { request : c . req . raw } ) ;
160- return c . json ( success ( { id, deleted : true } ) ) ;
161- } ) ;
162- } ) ;
163-
164- // Batch Operations
165- app . post ( `${ prefix } /data/:objectName/batch` , async ( c ) => {
166- return errorHandler ( c , async ( ) => {
167- const { objectName } = c . req . param ( ) ;
168- const { operations } = await c . req . json ( ) ;
169- const data = await kernel . broker . call ( 'data.batch' , { object : objectName , operations } , { request : c . req . raw } ) ;
170- return c . json ( success ( data ) ) ;
171- } ) ;
98+ const result = await dispatcher . handleData ( path , method , body , query , { request : c . req . raw } ) ;
99+ return normalizeResponse ( c , result ) ;
100+ } catch ( err : any ) {
101+ return c . json ( { success : false , error : { message : err . message , code : err . statusCode || 500 } } , err . statusCode || 500 ) ;
102+ }
172103 } ) ;
173104
174- // --- 4. Storage & Files ---
175-
176- // Upload File
177- app . post ( `${ prefix } /storage/upload` , async ( c ) => {
178- return errorHandler ( c , async ( ) => {
179- const storageService = ( kernel as any ) . getService ?.( 'file-storage' ) || ( kernel as any ) . services ?. [ 'file-storage' ] ;
180- if ( ! storageService ) throw { statusCode : 501 , message : 'File storage not configured' } ;
181-
182- const body = await c . req . parseBody ( ) ;
183- const file = body [ 'file' ] ;
184-
185- if ( ! file ) throw { statusCode : 400 , message : 'No file provided' } ;
105+ // --- 5. Storage Endpoints ---
106+ app . all ( `${ prefix } /storage*` , async ( c ) => {
107+ try {
108+ const path = c . req . path . substring ( c . req . path . indexOf ( '/storage' ) + 8 ) ;
109+ const method = c . req . method ;
186110
187- // Allow service to handle raw file object or buffer
188- const result = await storageService . upload ( file , { request : c . req . raw } ) ;
189- return c . json ( success ( result ) ) ;
190- } ) ;
191- } ) ;
192-
193- // Get File
194- app . get ( `${ prefix } /storage/file/:id` , async ( c ) => {
195- return errorHandler ( c , async ( ) => {
196- const storageService = ( kernel as any ) . getService ?.( 'file-storage' ) || ( kernel as any ) . services ?. [ 'file-storage' ] ;
197- if ( ! storageService ) throw { statusCode : 501 , message : 'File storage not configured' } ;
111+ let file : any = undefined ;
112+ if ( method === 'POST' && path . includes ( 'upload' ) ) {
113+ const body = await c . req . parseBody ( ) ;
114+ file = body [ 'file' ] ;
115+ }
198116
199- const { id } = c . req . param ( ) ;
200- const result = await storageService . download ( id , { request : c . req . raw } ) ;
201-
202- // If result is a stream or blob, return it.
203- // If result is a URL (e.g. S3 signed url), redirect.
204- if ( result . url && result . redirect ) {
205- return c . redirect ( result . url ) ;
206- }
207- if ( result . stream ) {
208- return c . body ( result . stream , 200 , {
209- 'Content-Type' : result . mimeType || 'application/octet-stream' ,
210- 'Content-Length' : result . size ,
211- } ) ;
212- }
213- return c . json ( success ( result ) ) ;
214- } ) ;
117+ const result = await dispatcher . handleStorage ( path , method , file , { request : c . req . raw } ) ;
118+ return normalizeResponse ( c , result ) ;
119+ } catch ( err : any ) {
120+ return c . json ( { success : false , error : { message : err . message , code : err . statusCode || 500 } } , err . statusCode || 500 ) ;
121+ }
215122 } ) ;
216123
217124 return app ;
0 commit comments