@@ -4,7 +4,8 @@ import { Plugin, PluginContext, IHttpServer, IDataEngine } from '@objectstack/co
44import {
55 RestServerConfig ,
66} from '@objectstack/spec/api' ;
7- import { HonoHttpServer } from './adapter' ;
7+ import { HonoHttpServer , HonoCorsOptions } from './adapter' ;
8+ import { cors } from 'hono/cors' ;
89import { serveStatic } from '@hono/node-server/serve-static' ;
910import * as fs from 'fs' ;
1011import * as path from 'path' ;
@@ -45,14 +46,22 @@ export interface HonoPluginOptions {
4546 * @default false
4647 */
4748 spaFallback ?: boolean ;
49+
50+ /**
51+ * CORS configuration. Set to `false` to disable entirely.
52+ * Enabled by default with origin '*'.
53+ * Can also be controlled via environment variables:
54+ * CORS_ENABLED, CORS_ORIGIN, CORS_CREDENTIALS, CORS_MAX_AGE
55+ */
56+ cors ?: HonoCorsOptions | false ;
4857}
4958
5059/**
5160 * Hono Server Plugin
52- *
61+ *
5362 * Provides HTTP server capabilities using Hono framework.
5463 * Registers the IHttpServer service so other plugins can register routes.
55- *
64+ *
5665 * Route registration is handled by plugins:
5766 * - `@objectstack/rest` → CRUD, metadata, discovery, UI, batch
5867 * - `createDispatcherPlugin()` → auth, graphql, analytics, packages, etc.
@@ -61,17 +70,17 @@ export class HonoServerPlugin implements Plugin {
6170 name = 'com.objectstack.server.hono' ;
6271 type = 'server' ;
6372 version = '0.9.0' ;
64-
73+
6574 // Constants
6675 private static readonly DEFAULT_ENDPOINT_PRIORITY = 100 ;
6776 private static readonly CORE_ENDPOINT_PRIORITY = 950 ;
6877 private static readonly DISCOVERY_ENDPOINT_PRIORITY = 900 ;
69-
78+
7079 private options : HonoPluginOptions ;
7180 private server : HonoHttpServer ;
7281
7382 constructor ( options : HonoPluginOptions = { } ) {
74- this . options = {
83+ this . options = {
7584 port : 3000 ,
7685 registerStandardEndpoints : true ,
7786 useApiRegistry : true ,
@@ -86,17 +95,61 @@ export class HonoServerPlugin implements Plugin {
8695 * Init phase - Setup HTTP server and register as service
8796 */
8897 init = async ( ctx : PluginContext ) => {
89- ctx . logger . debug ( 'Initializing Hono server plugin' , {
98+ ctx . logger . debug ( 'Initializing Hono server plugin' , {
9099 port : this . options . port ,
91- staticRoot : this . options . staticRoot
100+ staticRoot : this . options . staticRoot
92101 } ) ;
93-
102+
94103 // Register HTTP server service as IHttpServer
95104 // Register as 'http.server' to match core requirements
96105 ctx . registerService ( 'http.server' , this . server ) ;
97106 // Alias 'http-server' for backward compatibility
98107 ctx . registerService ( 'http-server' , this . server ) ;
99108 ctx . logger . debug ( 'HTTP server service registered' , { serviceName : 'http.server' } ) ;
109+
110+ // ─── CORS Middleware ──────────────────────────────────────────────────
111+ // Enabled by default. Controlled via options.cors or environment variables.
112+ const corsDisabledByEnv = process . env . CORS_ENABLED === 'false' ;
113+ if ( this . options . cors !== false && ! corsDisabledByEnv ) {
114+ const corsOpts = typeof this . options . cors === 'object' ? this . options . cors : { } ;
115+ const enabled = corsOpts . enabled ?? true ;
116+
117+ if ( enabled ) {
118+ let configuredOrigin : string | string [ ] ;
119+ if ( corsOpts . origins ) {
120+ configuredOrigin = corsOpts . origins ;
121+ } else if ( process . env . CORS_ORIGIN ) {
122+ const envOrigin = process . env . CORS_ORIGIN . trim ( ) ;
123+ configuredOrigin = envOrigin . includes ( ',' ) ? envOrigin . split ( ',' ) . map ( s => s . trim ( ) ) : envOrigin ;
124+ } else {
125+ configuredOrigin = '*' ;
126+ }
127+
128+ const credentials = corsOpts . credentials ?? ( process . env . CORS_CREDENTIALS !== 'false' ) ;
129+ const maxAge = corsOpts . maxAge ?? ( process . env . CORS_MAX_AGE ? parseInt ( process . env . CORS_MAX_AGE , 10 ) : 86400 ) ;
130+
131+ // When credentials is true, browsers reject wildcard '*' for Access-Control-Allow-Origin.
132+ // Use a function to reflect the request's Origin header instead.
133+ let origin : string | string [ ] | ( ( origin : string ) => string | undefined | null ) ;
134+ if ( credentials && configuredOrigin === '*' ) {
135+ origin = ( requestOrigin : string ) => requestOrigin || '*' ;
136+ } else {
137+ origin = configuredOrigin ;
138+ }
139+
140+ const rawApp = this . server . getRawApp ( ) ;
141+ rawApp . use ( '*' , cors ( {
142+ origin : origin as any ,
143+ allowMethods : corsOpts . methods || [ 'GET' , 'POST' , 'PUT' , 'DELETE' , 'PATCH' , 'HEAD' , 'OPTIONS' ] ,
144+ allowHeaders : [ 'Content-Type' , 'Authorization' , 'X-Requested-With' ] ,
145+ exposeHeaders : [ ] ,
146+ credentials,
147+ maxAge,
148+ } ) ) ;
149+
150+ ctx . logger . debug ( 'CORS middleware enabled' , { origin : configuredOrigin , credentials } ) ;
151+ }
152+ }
100153 }
101154
102155 /**
@@ -112,8 +165,8 @@ export class HonoServerPlugin implements Plugin {
112165 try {
113166 const rawKernel = ctx . getKernel ( ) as any ;
114167 if ( rawKernel . plugins ) {
115- const loadedPlugins = rawKernel . plugins instanceof Map
116- ? Array . from ( rawKernel . plugins . values ( ) )
168+ const loadedPlugins = rawKernel . plugins instanceof Map
169+ ? Array . from ( rawKernel . plugins . values ( ) )
117170 : Array . isArray ( rawKernel . plugins ) ? rawKernel . plugins : Object . values ( rawKernel . plugins ) ;
118171
119172 for ( const plugin of ( loadedPlugins as any [ ] ) ) {
@@ -123,10 +176,10 @@ export class HonoServerPlugin implements Plugin {
123176 // Derive base route from name: @org /console -> console
124177 const slug = plugin . slug || plugin . name . split ( '/' ) . pop ( ) ;
125178 const baseRoute = `/${ slug } ` ;
126-
127- ctx . logger . debug ( `Auto-mounting UI Plugin: ${ plugin . name } ` , {
128- path : baseRoute ,
129- root : plugin . staticPath
179+
180+ ctx . logger . debug ( `Auto-mounting UI Plugin: ${ plugin . name } ` , {
181+ path : baseRoute ,
182+ root : plugin . staticPath
130183 } ) ;
131184
132185 mounts . push ( {
@@ -161,7 +214,7 @@ export class HonoServerPlugin implements Plugin {
161214
162215 if ( mounts . length > 0 ) {
163216 const rawApp = this . server . getRawApp ( ) ;
164-
217+
165218 for ( const mount of mounts ) {
166219 const mountRoot = path . resolve ( process . cwd ( ) , mount . root ) ;
167220
@@ -173,22 +226,22 @@ export class HonoServerPlugin implements Plugin {
173226 const mountPath = mount . path || '/' ;
174227 const normalizedPath = mountPath . startsWith ( '/' ) ? mountPath : `/${ mountPath } ` ;
175228 const routePattern = normalizedPath === '/' ? '/*' : `${ normalizedPath . replace ( / \/ $ / , '' ) } /*` ;
176-
229+
177230 // Routes to register: both /mount and /mount/*
178231 const routes = normalizedPath === '/' ? [ routePattern ] : [ normalizedPath , routePattern ] ;
179232
180- ctx . logger . debug ( 'Mounting static files' , {
181- to : routes ,
182- from : mountRoot ,
183- rewrite : mount . rewrite ,
184- spa : mount . spa
233+ ctx . logger . debug ( 'Mounting static files' , {
234+ to : routes ,
235+ from : mountRoot ,
236+ rewrite : mount . rewrite ,
237+ spa : mount . spa
185238 } ) ;
186239
187240 routes . forEach ( route => {
188241 // 1. Serve Static Files
189242 rawApp . get (
190- route ,
191- serveStatic ( {
243+ route ,
244+ serveStatic ( {
192245 root : mount . root ,
193246 rewriteRequestPath : ( reqPath ) => {
194247 if ( mount . rewrite && normalizedPath !== '/' ) {
@@ -208,12 +261,12 @@ export class HonoServerPlugin implements Plugin {
208261 // Skip if API path check
209262 const config = this . options . restConfig || { } ;
210263 const basePath = config . api ?. basePath || '/api' ;
211-
264+
212265 if ( c . req . path . startsWith ( basePath ) ) {
213266 return next ( ) ;
214267 }
215268
216- return serveStatic ( {
269+ return serveStatic ( {
217270 root : mount . root ,
218271 rewriteRequestPath : ( ) => 'index.html'
219272 } ) ( c , next ) ;
@@ -232,7 +285,7 @@ export class HonoServerPlugin implements Plugin {
232285
233286 const port = this . options . port ?? 3000 ;
234287 ctx . logger . debug ( 'Starting HTTP server' , { port } ) ;
235-
288+
236289 await this . server . listen ( port ) ;
237290
238291 const actualPort = this . server . getPort ( ) ;
0 commit comments