@objectstack/plugin-hono-server 1.0.5 → 1.0.7

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/plugin-hono-server@1.0.5 build /home/runner/work/spec/spec/packages/plugins/plugin-hono-server
2
+ > @objectstack/plugin-hono-server@1.0.7 build /home/runner/work/spec/spec/packages/plugins/plugin-hono-server
3
3
  > tsup --config ../../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.mjs 7.97 KB
14
- ESM dist/index.mjs.map 14.84 KB
15
- ESM ⚡️ Build success in 23ms
16
- CJS dist/index.js 8.32 KB
17
- CJS dist/index.js.map 14.84 KB
18
- CJS ⚡️ Build success in 23ms
13
+ ESM dist/index.mjs 11.09 KB
14
+ ESM dist/index.mjs.map 21.33 KB
15
+ ESM ⚡️ Build success in 21ms
16
+ CJS dist/index.js 12.04 KB
17
+ CJS dist/index.js.map 21.33 KB
18
+ CJS ⚡️ Build success in 21ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 3391ms
21
- DTS dist/index.d.mts 2.62 KB
22
- DTS dist/index.d.ts 2.62 KB
20
+ DTS ⚡️ Build success in 3051ms
21
+ DTS dist/index.d.mts 2.85 KB
22
+ DTS dist/index.d.ts 2.85 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @objectstack/plugin-hono-server
2
2
 
3
+ ## 1.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - ebdf787: feat: implement standard service discovery via `/.well-known/objectstack`
8
+ - Updated dependencies [ebdf787]
9
+ - @objectstack/runtime@1.0.7
10
+ - @objectstack/hono@1.0.7
11
+ - @objectstack/spec@1.0.7
12
+ - @objectstack/core@1.0.7
13
+ - @objectstack/types@1.0.7
14
+
15
+ ## 1.0.6
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [a7f7b9d]
20
+ - @objectstack/spec@1.0.6
21
+ - @objectstack/core@1.0.6
22
+ - @objectstack/runtime@1.0.6
23
+ - @objectstack/types@1.0.6
24
+ - @objectstack/hono@1.0.6
25
+
3
26
  ## 1.0.5
4
27
 
5
28
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -4,9 +4,19 @@ import { RestServerConfig } from '@objectstack/spec/api';
4
4
  import * as hono_types from 'hono/types';
5
5
  import { Hono } from 'hono';
6
6
 
7
+ interface StaticMount {
8
+ root: string;
9
+ path?: string;
10
+ rewrite?: boolean;
11
+ spa?: boolean;
12
+ }
7
13
  interface HonoPluginOptions {
8
14
  port?: number;
9
15
  staticRoot?: string;
16
+ /**
17
+ * Multiple static resource mounts
18
+ */
19
+ staticMounts?: StaticMount[];
10
20
  /**
11
21
  * REST server configuration
12
22
  * Controls automatic endpoint generation and API behavior
@@ -37,6 +47,7 @@ interface HonoPluginOptions {
37
47
  */
38
48
  declare class HonoServerPlugin implements Plugin {
39
49
  name: string;
50
+ type: string;
40
51
  version: string;
41
52
  private static readonly DEFAULT_ENDPOINT_PRIORITY;
42
53
  private static readonly CORE_ENDPOINT_PRIORITY;
@@ -85,4 +96,4 @@ declare class HonoHttpServer implements IHttpServer {
85
96
  close(): Promise<void>;
86
97
  }
87
98
 
88
- export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin };
99
+ export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount };
package/dist/index.d.ts CHANGED
@@ -4,9 +4,19 @@ import { RestServerConfig } from '@objectstack/spec/api';
4
4
  import * as hono_types from 'hono/types';
5
5
  import { Hono } from 'hono';
6
6
 
7
+ interface StaticMount {
8
+ root: string;
9
+ path?: string;
10
+ rewrite?: boolean;
11
+ spa?: boolean;
12
+ }
7
13
  interface HonoPluginOptions {
8
14
  port?: number;
9
15
  staticRoot?: string;
16
+ /**
17
+ * Multiple static resource mounts
18
+ */
19
+ staticMounts?: StaticMount[];
10
20
  /**
11
21
  * REST server configuration
12
22
  * Controls automatic endpoint generation and API behavior
@@ -37,6 +47,7 @@ interface HonoPluginOptions {
37
47
  */
38
48
  declare class HonoServerPlugin implements Plugin {
39
49
  name: string;
50
+ type: string;
40
51
  version: string;
41
52
  private static readonly DEFAULT_ENDPOINT_PRIORITY;
42
53
  private static readonly CORE_ENDPOINT_PRIORITY;
@@ -85,4 +96,4 @@ declare class HonoHttpServer implements IHttpServer {
85
96
  close(): Promise<void>;
86
97
  }
87
98
 
88
- export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin };
99
+ export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
9
  var __export = (target, all) => {
@@ -17,6 +19,14 @@ var __copyProps = (to, from, except, desc) => {
17
19
  return to;
18
20
  };
19
21
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
+ // If the importer is in node compatibility mode or this is not an ESM
24
+ // file that has been converted to a CommonJS file using a Babel-
25
+ // compatible transform (i.e. "__esModule" has not been set), then set
26
+ // "default" to the CommonJS "module.exports" for node compatibility.
27
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
+ mod
29
+ ));
20
30
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
31
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
22
32
 
@@ -94,20 +104,20 @@ var HonoHttpServer = class {
94
104
  return capturedResponse;
95
105
  };
96
106
  }
97
- get(path, handler) {
98
- this.app.get(path, this.wrap(handler));
107
+ get(path2, handler) {
108
+ this.app.get(path2, this.wrap(handler));
99
109
  }
100
- post(path, handler) {
101
- this.app.post(path, this.wrap(handler));
110
+ post(path2, handler) {
111
+ this.app.post(path2, this.wrap(handler));
102
112
  }
103
- put(path, handler) {
104
- this.app.put(path, this.wrap(handler));
113
+ put(path2, handler) {
114
+ this.app.put(path2, this.wrap(handler));
105
115
  }
106
- delete(path, handler) {
107
- this.app.delete(path, this.wrap(handler));
116
+ delete(path2, handler) {
117
+ this.app.delete(path2, this.wrap(handler));
108
118
  }
109
- patch(path, handler) {
110
- this.app.patch(path, this.wrap(handler));
119
+ patch(path2, handler) {
120
+ this.app.patch(path2, this.wrap(handler));
111
121
  }
112
122
  use(pathOrHandler, handler) {
113
123
  if (typeof pathOrHandler === "string" && handler) {
@@ -123,11 +133,11 @@ var HonoHttpServer = class {
123
133
  /**
124
134
  * Mount a sub-application or router
125
135
  */
126
- mount(path, subApp) {
127
- this.app.route(path, subApp);
136
+ mount(path2, subApp) {
137
+ this.app.route(path2, subApp);
128
138
  }
129
139
  async listen(port) {
130
- return new Promise((resolve) => {
140
+ return new Promise((resolve2) => {
131
141
  if (this.staticRoot) {
132
142
  this.app.get("/*", (0, import_serve_static.serveStatic)({ root: this.staticRoot }));
133
143
  }
@@ -137,7 +147,7 @@ var HonoHttpServer = class {
137
147
  port: targetPort
138
148
  }, (info) => {
139
149
  this.listeningPort = info.port;
140
- resolve();
150
+ resolve2();
141
151
  });
142
152
  });
143
153
  }
@@ -158,9 +168,12 @@ var HonoHttpServer = class {
158
168
  // src/hono-plugin.ts
159
169
  var import_hono2 = require("@objectstack/hono");
160
170
  var import_serve_static2 = require("@hono/node-server/serve-static");
171
+ var fs = __toESM(require("fs"));
172
+ var path = __toESM(require("path"));
161
173
  var HonoServerPlugin = class {
162
174
  constructor(options = {}) {
163
175
  __publicField(this, "name", "com.objectstack.server.hono");
176
+ __publicField(this, "type", "server");
164
177
  __publicField(this, "version", "0.9.0");
165
178
  __publicField(this, "options");
166
179
  __publicField(this, "server");
@@ -194,25 +207,99 @@ var HonoServerPlugin = class {
194
207
  });
195
208
  ctx.logger.debug("Mounting ObjectStack Runtime App", { prefix: apiPath });
196
209
  this.server.mount("/", app);
210
+ const rawApp = this.server.getRawApp();
211
+ rawApp.get("/.well-known/objectstack", (c) => {
212
+ return c.redirect(apiPath);
213
+ });
214
+ ctx.logger.debug("Registered standard discovery endpoint", { path: "/.well-known/objectstack", target: apiPath });
197
215
  } catch (e) {
198
216
  ctx.logger.error("Failed to create standard Hono app", e);
199
217
  }
218
+ const mounts = this.options.staticMounts || [];
219
+ try {
220
+ const rawKernel = ctx.getKernel();
221
+ if (rawKernel.plugins) {
222
+ const loadedPlugins = rawKernel.plugins instanceof Map ? Array.from(rawKernel.plugins.values()) : Array.isArray(rawKernel.plugins) ? rawKernel.plugins : Object.values(rawKernel.plugins);
223
+ for (const plugin of loadedPlugins) {
224
+ if ((plugin.type === "ui" || plugin.type === "ui-plugin") && plugin.staticPath) {
225
+ const slug = plugin.slug || plugin.name.split("/").pop();
226
+ const baseRoute = `/${slug}`;
227
+ ctx.logger.debug(`Auto-mounting UI Plugin: ${plugin.name}`, {
228
+ path: baseRoute,
229
+ root: plugin.staticPath
230
+ });
231
+ mounts.push({
232
+ root: plugin.staticPath,
233
+ path: baseRoute,
234
+ rewrite: true,
235
+ // Strip prefix: /console/assets/x -> /assets/x
236
+ spa: true
237
+ });
238
+ if (plugin.default || plugin.isDefault) {
239
+ const rawApp = this.server.getRawApp();
240
+ rawApp.get("/", (c) => c.redirect(baseRoute));
241
+ ctx.logger.debug(`Set default UI redirect: / -> ${baseRoute}`);
242
+ }
243
+ }
244
+ }
245
+ }
246
+ } catch (err) {
247
+ ctx.logger.warn("Failed to auto-discover UI plugins", { error: err.message || err });
248
+ }
200
249
  if (this.options.staticRoot) {
250
+ mounts.push({
251
+ root: this.options.staticRoot,
252
+ path: "/",
253
+ rewrite: false,
254
+ spa: this.options.spaFallback
255
+ });
256
+ }
257
+ if (mounts.length > 0) {
201
258
  const rawApp = this.server.getRawApp();
202
- const staticRoot = this.options.staticRoot;
203
- ctx.logger.debug("Configuring static files", { root: staticRoot, spa: this.options.spaFallback });
204
- rawApp.get("/*", (0, import_serve_static2.serveStatic)({ root: staticRoot }));
205
- if (this.options.spaFallback) {
206
- rawApp.get("*", async (c, next) => {
207
- const config = this.options.restConfig || {};
208
- const basePath = config.api?.basePath || "/api";
209
- if (c.req.path.startsWith(basePath)) {
210
- return next();
259
+ for (const mount of mounts) {
260
+ const mountRoot = path.resolve(process.cwd(), mount.root);
261
+ if (!fs.existsSync(mountRoot)) {
262
+ ctx.logger.warn(`Static mount root not found: ${mountRoot}. Skipping.`);
263
+ continue;
264
+ }
265
+ const mountPath = mount.path || "/";
266
+ const normalizedPath = mountPath.startsWith("/") ? mountPath : `/${mountPath}`;
267
+ const routePattern = normalizedPath === "/" ? "/*" : `${normalizedPath.replace(/\/$/, "")}/*`;
268
+ const routes = normalizedPath === "/" ? [routePattern] : [normalizedPath, routePattern];
269
+ ctx.logger.debug("Mounting static files", {
270
+ to: routes,
271
+ from: mountRoot,
272
+ rewrite: mount.rewrite,
273
+ spa: mount.spa
274
+ });
275
+ routes.forEach((route) => {
276
+ rawApp.get(
277
+ route,
278
+ (0, import_serve_static2.serveStatic)({
279
+ root: mount.root,
280
+ rewriteRequestPath: (reqPath) => {
281
+ if (mount.rewrite && normalizedPath !== "/") {
282
+ if (reqPath.startsWith(normalizedPath)) {
283
+ return reqPath.substring(normalizedPath.length) || "/";
284
+ }
285
+ }
286
+ return reqPath;
287
+ }
288
+ })
289
+ );
290
+ if (mount.spa) {
291
+ rawApp.get(route, async (c, next) => {
292
+ const config = this.options.restConfig || {};
293
+ const basePath = config.api?.basePath || "/api";
294
+ if (c.req.path.startsWith(basePath)) {
295
+ return next();
296
+ }
297
+ return (0, import_serve_static2.serveStatic)({
298
+ root: mount.root,
299
+ rewriteRequestPath: () => "index.html"
300
+ })(c, next);
301
+ });
211
302
  }
212
- return (0, import_serve_static2.serveStatic)({
213
- root: staticRoot,
214
- rewriteRequestPath: () => "index.html"
215
- })(c, next);
216
303
  });
217
304
  }
218
305
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/hono-plugin.ts"],"sourcesContent":["export * from './hono-plugin';\nexport * from './adapter';\n\n","// Export IHttpServer from core\nexport * from '@objectstack/core';\n\nimport { \n IHttpServer, \n RouteHandler, \n Middleware \n} from '@objectstack/core';\nimport { Hono } from 'hono';\nimport { serve } from '@hono/node-server';\nimport { serveStatic } from '@hono/node-server/serve-static';\n\n/**\n * Hono Implementation of IHttpServer\n */\nexport class HonoHttpServer implements IHttpServer {\n private app: Hono;\n private server: any;\n private listeningPort: number | undefined;\n\n constructor(\n private port: number = 3000,\n private staticRoot?: string\n ) {\n this.app = new Hono();\n }\n\n // internal helper to convert standard handler to Hono handler\n private wrap(handler: RouteHandler) {\n return async (c: any) => {\n let body: any = {};\n \n // Try to parse JSON body first if content-type is JSON\n if (c.req.header('content-type')?.includes('application/json')) {\n try { \n body = await c.req.json(); \n } catch(e) {\n // If JSON parsing fails, try parseBody\n try { \n body = await c.req.parseBody(); \n } catch(e2) {}\n }\n } else {\n // For non-JSON content types, use parseBody\n try { \n body = await c.req.parseBody(); \n } catch(e) {}\n }\n \n const req = {\n params: c.req.param(),\n query: c.req.query(),\n body,\n headers: c.req.header(),\n method: c.req.method,\n path: c.req.path\n };\n\n let capturedResponse: any;\n\n const res = {\n json: (data: any) => { capturedResponse = c.json(data); },\n send: (data: string) => { capturedResponse = c.html(data); },\n status: (code: number) => { c.status(code); return res; },\n header: (name: string, value: string) => { c.header(name, value); return res; }\n };\n\n await handler(req as any, res as any);\n return capturedResponse;\n };\n }\n\n get(path: string, handler: RouteHandler) {\n this.app.get(path, this.wrap(handler));\n }\n post(path: string, handler: RouteHandler) {\n this.app.post(path, this.wrap(handler));\n }\n put(path: string, handler: RouteHandler) {\n this.app.put(path, this.wrap(handler));\n }\n delete(path: string, handler: RouteHandler) {\n this.app.delete(path, this.wrap(handler));\n }\n patch(path: string, handler: RouteHandler) {\n this.app.patch(path, this.wrap(handler));\n }\n \n use(pathOrHandler: string | Middleware, handler?: Middleware) {\n if (typeof pathOrHandler === 'string' && handler) {\n // Path based middleware\n // Hono middleware signature is different (c, next) => ...\n this.app.use(pathOrHandler, async (c, next) => {\n // Simplistic conversion\n await handler({} as any, {} as any, next);\n });\n } else if (typeof pathOrHandler === 'function') {\n // Global middleware\n this.app.use('*', async (c, next) => {\n await pathOrHandler({} as any, {} as any, next);\n });\n }\n }\n\n /**\n * Mount a sub-application or router\n */\n mount(path: string, subApp: Hono) {\n this.app.route(path, subApp);\n }\n\n\n async listen(port: number) {\n return new Promise<void>((resolve) => {\n if (this.staticRoot) {\n this.app.get('/*', serveStatic({ root: this.staticRoot }));\n }\n \n const targetPort = port || this.port;\n this.server = serve({\n fetch: this.app.fetch,\n port: targetPort\n }, (info) => {\n this.listeningPort = info.port;\n resolve();\n });\n });\n }\n\n getPort() {\n return this.listeningPort || this.port;\n }\n\n // Expose raw app for scenarios where standard interface is not enough\n getRawApp() {\n return this.app;\n }\n\n async close() {\n if (this.server && typeof this.server.close === 'function') {\n this.server.close();\n }\n }\n\n\n}\n","import { Plugin, PluginContext, IHttpServer, ApiRegistry } from '@objectstack/core';\nimport { ObjectStackProtocol } from '@objectstack/spec/api';\nimport { \n ApiRegistryEntryInput,\n ApiEndpointRegistrationInput,\n RestServerConfig,\n} from '@objectstack/spec/api';\nimport { HonoHttpServer } from './adapter';\nimport { createHonoApp } from '@objectstack/hono';\nimport { serveStatic } from '@hono/node-server/serve-static';\n\nexport interface HonoPluginOptions {\n port?: number;\n staticRoot?: string;\n /**\n * REST server configuration\n * Controls automatic endpoint generation and API behavior\n */\n restConfig?: RestServerConfig;\n /**\n * Whether to register standard ObjectStack CRUD endpoints\n * @default true\n */\n registerStandardEndpoints?: boolean;\n /**\n * Whether to load endpoints from API Registry\n * @default true\n */\n useApiRegistry?: boolean;\n\n /**\n * Whether to enable SPA fallback\n * If true, returns index.html for non-API 404s\n * @default false\n */\n spaFallback?: boolean;\n}\n\n/**\n * Hono Server Plugin\n * \n * Provides HTTP server capabilities using Hono framework.\n * Registers routes for ObjectStack Runtime Protocol.\n */\nexport class HonoServerPlugin implements Plugin {\n name = 'com.objectstack.server.hono';\n version = '0.9.0';\n \n // Constants\n private static readonly DEFAULT_ENDPOINT_PRIORITY = 100;\n private static readonly CORE_ENDPOINT_PRIORITY = 950;\n private static readonly DISCOVERY_ENDPOINT_PRIORITY = 900;\n \n private options: HonoPluginOptions;\n private server: HonoHttpServer;\n\n constructor(options: HonoPluginOptions = {}) {\n this.options = { \n port: 3000,\n registerStandardEndpoints: true,\n useApiRegistry: true,\n spaFallback: false,\n ...options\n };\n // We handle static root manually in start() to support SPA fallback\n this.server = new HonoHttpServer(this.options.port);\n }\n\n /**\n * Init phase - Setup HTTP server and register as service\n */\n init = async (ctx: PluginContext) => {\n ctx.logger.debug('Initializing Hono server plugin', { \n port: this.options.port,\n staticRoot: this.options.staticRoot \n });\n \n // Register HTTP server service as IHttpServer\n // Register as 'http.server' to match core requirements\n ctx.registerService('http.server', this.server);\n // Alias 'http-server' for backward compatibility\n ctx.registerService('http-server', this.server);\n ctx.logger.debug('HTTP server service registered', { serviceName: 'http.server' });\n }\n\n /**\n * Start phase - Bind routes and start listening\n */\n start = async (ctx: PluginContext) => {\n ctx.logger.debug('Starting Hono server plugin');\n \n // Use Standard ObjectStack Runtime Hono App\n try {\n const kernel = ctx.getKernel();\n const config = this.options.restConfig || {};\n // Calculate prefix similar to before\n const apiVersion = config.api?.version || 'v1';\n const basePath = config.api?.basePath || '/api';\n const apiPath = config.api?.apiPath || `${basePath}/${apiVersion}`;\n \n const app = createHonoApp({ \n kernel,\n prefix: apiPath // Use the calculated path\n });\n \n ctx.logger.debug('Mounting ObjectStack Runtime App', { prefix: apiPath });\n // Use the mount method we added to HonoHttpServer\n this.server.mount('/', app as any);\n\n } catch (e: any) {\n ctx.logger.error('Failed to create standard Hono app', e);\n }\n\n // Configure Static Files & SPA Fallback\n if (this.options.staticRoot) {\n const rawApp = this.server.getRawApp();\n const staticRoot = this.options.staticRoot;\n \n ctx.logger.debug('Configuring static files', { root: staticRoot, spa: this.options.spaFallback });\n \n // 1. Static Files\n rawApp.get('/*', serveStatic({ root: staticRoot }));\n \n // 2. SPA Fallback\n if (this.options.spaFallback) {\n rawApp.get('*', async (c, next) => {\n // Skip API paths\n const config = this.options.restConfig || {};\n const basePath = config.api?.basePath || '/api';\n \n if (c.req.path.startsWith(basePath)) {\n return next();\n }\n \n // Fallback to index.html\n return serveStatic({ \n root: staticRoot,\n rewriteRequestPath: () => 'index.html'\n })(c, next);\n });\n }\n }\n\n // Start server on kernel:ready hook\n ctx.hook('kernel:ready', async () => {\n const port = this.options.port || 3000;\n ctx.logger.debug('Starting HTTP server', { port });\n \n await this.server.listen(port);\n \n const actualPort = this.server.getPort();\n ctx.logger.info('HTTP server started successfully', { \n port: actualPort, \n url: `http://localhost:${actualPort}` \n });\n });\n }\n\n /**\n * Destroy phase - Stop server\n */\n async destroy() {\n this.server.close();\n // Note: Can't use ctx.logger here since we're in destroy\n console.log('[HonoServerPlugin] Server stopped');\n }\n}\n"],"mappings":"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","names":["import_hono","import_serve_static"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/hono-plugin.ts"],"sourcesContent":["export * from './hono-plugin';\nexport * from './adapter';\n\n","// Export IHttpServer from core\nexport * from '@objectstack/core';\n\nimport { \n IHttpServer, \n RouteHandler, \n Middleware \n} from '@objectstack/core';\nimport { Hono } from 'hono';\nimport { serve } from '@hono/node-server';\nimport { serveStatic } from '@hono/node-server/serve-static';\n\n/**\n * Hono Implementation of IHttpServer\n */\nexport class HonoHttpServer implements IHttpServer {\n private app: Hono;\n private server: any;\n private listeningPort: number | undefined;\n\n constructor(\n private port: number = 3000,\n private staticRoot?: string\n ) {\n this.app = new Hono();\n }\n\n // internal helper to convert standard handler to Hono handler\n private wrap(handler: RouteHandler) {\n return async (c: any) => {\n let body: any = {};\n \n // Try to parse JSON body first if content-type is JSON\n if (c.req.header('content-type')?.includes('application/json')) {\n try { \n body = await c.req.json(); \n } catch(e) {\n // If JSON parsing fails, try parseBody\n try { \n body = await c.req.parseBody(); \n } catch(e2) {}\n }\n } else {\n // For non-JSON content types, use parseBody\n try { \n body = await c.req.parseBody(); \n } catch(e) {}\n }\n \n const req = {\n params: c.req.param(),\n query: c.req.query(),\n body,\n headers: c.req.header(),\n method: c.req.method,\n path: c.req.path\n };\n\n let capturedResponse: any;\n\n const res = {\n json: (data: any) => { capturedResponse = c.json(data); },\n send: (data: string) => { capturedResponse = c.html(data); },\n status: (code: number) => { c.status(code); return res; },\n header: (name: string, value: string) => { c.header(name, value); return res; }\n };\n\n await handler(req as any, res as any);\n return capturedResponse;\n };\n }\n\n get(path: string, handler: RouteHandler) {\n this.app.get(path, this.wrap(handler));\n }\n post(path: string, handler: RouteHandler) {\n this.app.post(path, this.wrap(handler));\n }\n put(path: string, handler: RouteHandler) {\n this.app.put(path, this.wrap(handler));\n }\n delete(path: string, handler: RouteHandler) {\n this.app.delete(path, this.wrap(handler));\n }\n patch(path: string, handler: RouteHandler) {\n this.app.patch(path, this.wrap(handler));\n }\n \n use(pathOrHandler: string | Middleware, handler?: Middleware) {\n if (typeof pathOrHandler === 'string' && handler) {\n // Path based middleware\n // Hono middleware signature is different (c, next) => ...\n this.app.use(pathOrHandler, async (c, next) => {\n // Simplistic conversion\n await handler({} as any, {} as any, next);\n });\n } else if (typeof pathOrHandler === 'function') {\n // Global middleware\n this.app.use('*', async (c, next) => {\n await pathOrHandler({} as any, {} as any, next);\n });\n }\n }\n\n /**\n * Mount a sub-application or router\n */\n mount(path: string, subApp: Hono) {\n this.app.route(path, subApp);\n }\n\n\n async listen(port: number) {\n return new Promise<void>((resolve) => {\n if (this.staticRoot) {\n this.app.get('/*', serveStatic({ root: this.staticRoot }));\n }\n \n const targetPort = port || this.port;\n this.server = serve({\n fetch: this.app.fetch,\n port: targetPort\n }, (info) => {\n this.listeningPort = info.port;\n resolve();\n });\n });\n }\n\n getPort() {\n return this.listeningPort || this.port;\n }\n\n // Expose raw app for scenarios where standard interface is not enough\n getRawApp() {\n return this.app;\n }\n\n async close() {\n if (this.server && typeof this.server.close === 'function') {\n this.server.close();\n }\n }\n\n\n}\n","import { Plugin, PluginContext, IHttpServer, ApiRegistry } from '@objectstack/core';\nimport { ObjectStackProtocol } from '@objectstack/spec/api';\nimport { \n ApiRegistryEntryInput,\n ApiEndpointRegistrationInput,\n RestServerConfig,\n} from '@objectstack/spec/api';\nimport { HonoHttpServer } from './adapter';\nimport { createHonoApp } from '@objectstack/hono';\nimport { serveStatic } from '@hono/node-server/serve-static';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface StaticMount {\n root: string;\n path?: string;\n rewrite?: boolean;\n spa?: boolean;\n}\n\nexport interface HonoPluginOptions {\n port?: number;\n staticRoot?: string;\n /**\n * Multiple static resource mounts\n */\n staticMounts?: StaticMount[];\n /**\n * REST server configuration\n * Controls automatic endpoint generation and API behavior\n */\n restConfig?: RestServerConfig;\n /**\n * Whether to register standard ObjectStack CRUD endpoints\n * @default true\n */\n registerStandardEndpoints?: boolean;\n /**\n * Whether to load endpoints from API Registry\n * @default true\n */\n useApiRegistry?: boolean;\n\n /**\n * Whether to enable SPA fallback\n * If true, returns index.html for non-API 404s\n * @default false\n */\n spaFallback?: boolean;\n}\n\n/**\n * Hono Server Plugin\n * \n * Provides HTTP server capabilities using Hono framework.\n * Registers routes for ObjectStack Runtime Protocol.\n */\nexport class HonoServerPlugin implements Plugin {\n name = 'com.objectstack.server.hono';\n type = 'server';\n version = '0.9.0';\n \n // Constants\n private static readonly DEFAULT_ENDPOINT_PRIORITY = 100;\n private static readonly CORE_ENDPOINT_PRIORITY = 950;\n private static readonly DISCOVERY_ENDPOINT_PRIORITY = 900;\n \n private options: HonoPluginOptions;\n private server: HonoHttpServer;\n\n constructor(options: HonoPluginOptions = {}) {\n this.options = { \n port: 3000,\n registerStandardEndpoints: true,\n useApiRegistry: true,\n spaFallback: false,\n ...options\n };\n // We handle static root manually in start() to support SPA fallback\n this.server = new HonoHttpServer(this.options.port);\n }\n\n /**\n * Init phase - Setup HTTP server and register as service\n */\n init = async (ctx: PluginContext) => {\n ctx.logger.debug('Initializing Hono server plugin', { \n port: this.options.port,\n staticRoot: this.options.staticRoot \n });\n \n // Register HTTP server service as IHttpServer\n // Register as 'http.server' to match core requirements\n ctx.registerService('http.server', this.server);\n // Alias 'http-server' for backward compatibility\n ctx.registerService('http-server', this.server);\n ctx.logger.debug('HTTP server service registered', { serviceName: 'http.server' });\n }\n\n /**\n * Start phase - Bind routes and start listening\n */\n start = async (ctx: PluginContext) => {\n ctx.logger.debug('Starting Hono server plugin');\n \n // Use Standard ObjectStack Runtime Hono App\n try {\n const kernel = ctx.getKernel();\n const config = this.options.restConfig || {};\n // Calculate prefix similar to before\n const apiVersion = config.api?.version || 'v1';\n const basePath = config.api?.basePath || '/api';\n const apiPath = config.api?.apiPath || `${basePath}/${apiVersion}`;\n \n const app = createHonoApp({ \n kernel,\n prefix: apiPath // Use the calculated path\n });\n \n ctx.logger.debug('Mounting ObjectStack Runtime App', { prefix: apiPath });\n // Use the mount method we added to HonoHttpServer\n this.server.mount('/', app as any);\n\n // Register Standard Discovery Endpoint\n const rawApp = this.server.getRawApp();\n rawApp.get('/.well-known/objectstack', (c) => {\n return c.redirect(apiPath);\n });\n ctx.logger.debug('Registered standard discovery endpoint', { path: '/.well-known/objectstack', target: apiPath });\n\n } catch (e: any) {\n ctx.logger.error('Failed to create standard Hono app', e);\n }\n\n // Configure Static Files & SPA Fallback\n const mounts: StaticMount[] = this.options.staticMounts || [];\n\n // Auto-discover UI Plugins\n try {\n const rawKernel = ctx.getKernel() as any;\n if (rawKernel.plugins) {\n const loadedPlugins = rawKernel.plugins instanceof Map \n ? Array.from(rawKernel.plugins.values()) \n : Array.isArray(rawKernel.plugins) ? rawKernel.plugins : Object.values(rawKernel.plugins);\n\n for (const plugin of (loadedPlugins as any[])) {\n // Check for UI Plugin signature\n // Support legacy 'ui-plugin' and new 'ui' type\n if ((plugin.type === 'ui' || plugin.type === 'ui-plugin') && plugin.staticPath) {\n // Derive base route from name: @org/console -> console\n const slug = plugin.slug || plugin.name.split('/').pop();\n const baseRoute = `/${slug}`;\n \n ctx.logger.debug(`Auto-mounting UI Plugin: ${plugin.name}`, { \n path: baseRoute, \n root: plugin.staticPath \n });\n\n mounts.push({\n root: plugin.staticPath,\n path: baseRoute,\n rewrite: true, // Strip prefix: /console/assets/x -> /assets/x\n spa: true\n });\n\n // Handle Default Plugin Redirect\n if (plugin.default || plugin.isDefault) {\n const rawApp = this.server.getRawApp();\n rawApp.get('/', (c) => c.redirect(baseRoute));\n ctx.logger.debug(`Set default UI redirect: / -> ${baseRoute}`);\n }\n }\n }\n }\n } catch (err: any) {\n ctx.logger.warn('Failed to auto-discover UI plugins', { error: err.message || err });\n }\n\n // Backward compatibility for staticRoot\n if (this.options.staticRoot) {\n mounts.push({\n root: this.options.staticRoot,\n path: '/',\n rewrite: false,\n spa: this.options.spaFallback\n });\n }\n\n if (mounts.length > 0) {\n const rawApp = this.server.getRawApp();\n \n for (const mount of mounts) {\n const mountRoot = path.resolve(process.cwd(), mount.root);\n\n if (!fs.existsSync(mountRoot)) {\n ctx.logger.warn(`Static mount root not found: ${mountRoot}. Skipping.`);\n continue;\n }\n\n const mountPath = mount.path || '/';\n const normalizedPath = mountPath.startsWith('/') ? mountPath : `/${mountPath}`;\n const routePattern = normalizedPath === '/' ? '/*' : `${normalizedPath.replace(/\\/$/, '')}/*`;\n \n // Routes to register: both /mount and /mount/*\n const routes = normalizedPath === '/' ? [routePattern] : [normalizedPath, routePattern];\n\n ctx.logger.debug('Mounting static files', { \n to: routes, \n from: mountRoot, \n rewrite: mount.rewrite, \n spa: mount.spa \n });\n\n routes.forEach(route => {\n // 1. Serve Static Files\n rawApp.get(\n route, \n serveStatic({ \n root: mount.root,\n rewriteRequestPath: (reqPath) => {\n if (mount.rewrite && normalizedPath !== '/') {\n // /console/assets/style.css -> /assets/style.css\n if (reqPath.startsWith(normalizedPath)) {\n return reqPath.substring(normalizedPath.length) || '/';\n }\n }\n return reqPath;\n }\n })\n );\n\n // 2. SPA Fallback (Scoped)\n if (mount.spa) {\n rawApp.get(route, async (c, next) => {\n // Skip if API path check\n const config = this.options.restConfig || {};\n const basePath = config.api?.basePath || '/api';\n \n if (c.req.path.startsWith(basePath)) {\n return next();\n }\n\n return serveStatic({ \n root: mount.root,\n rewriteRequestPath: () => 'index.html'\n })(c, next);\n });\n }\n });\n }\n }\n\n // Start server on kernel:ready hook\n ctx.hook('kernel:ready', async () => {\n const port = this.options.port || 3000;\n ctx.logger.debug('Starting HTTP server', { port });\n \n await this.server.listen(port);\n \n const actualPort = this.server.getPort();\n ctx.logger.info('HTTP server started successfully', { \n port: actualPort, \n url: `http://localhost:${actualPort}` \n });\n });\n }\n\n /**\n * Destroy phase - Stop server\n */\n async destroy() {\n this.server.close();\n // Note: Can't use ctx.logger here since we're in destroy\n console.log('[HonoServerPlugin] Server stopped');\n }\n}\n"],"mappings":"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","names":["path","resolve","import_hono","import_serve_static"]}
package/dist/index.mjs CHANGED
@@ -92,20 +92,20 @@ var HonoHttpServer = class {
92
92
  return capturedResponse;
93
93
  };
94
94
  }
95
- get(path, handler) {
96
- this.app.get(path, this.wrap(handler));
95
+ get(path2, handler) {
96
+ this.app.get(path2, this.wrap(handler));
97
97
  }
98
- post(path, handler) {
99
- this.app.post(path, this.wrap(handler));
98
+ post(path2, handler) {
99
+ this.app.post(path2, this.wrap(handler));
100
100
  }
101
- put(path, handler) {
102
- this.app.put(path, this.wrap(handler));
101
+ put(path2, handler) {
102
+ this.app.put(path2, this.wrap(handler));
103
103
  }
104
- delete(path, handler) {
105
- this.app.delete(path, this.wrap(handler));
104
+ delete(path2, handler) {
105
+ this.app.delete(path2, this.wrap(handler));
106
106
  }
107
- patch(path, handler) {
108
- this.app.patch(path, this.wrap(handler));
107
+ patch(path2, handler) {
108
+ this.app.patch(path2, this.wrap(handler));
109
109
  }
110
110
  use(pathOrHandler, handler) {
111
111
  if (typeof pathOrHandler === "string" && handler) {
@@ -121,11 +121,11 @@ var HonoHttpServer = class {
121
121
  /**
122
122
  * Mount a sub-application or router
123
123
  */
124
- mount(path, subApp) {
125
- this.app.route(path, subApp);
124
+ mount(path2, subApp) {
125
+ this.app.route(path2, subApp);
126
126
  }
127
127
  async listen(port) {
128
- return new Promise((resolve) => {
128
+ return new Promise((resolve2) => {
129
129
  if (this.staticRoot) {
130
130
  this.app.get("/*", serveStatic({ root: this.staticRoot }));
131
131
  }
@@ -135,7 +135,7 @@ var HonoHttpServer = class {
135
135
  port: targetPort
136
136
  }, (info) => {
137
137
  this.listeningPort = info.port;
138
- resolve();
138
+ resolve2();
139
139
  });
140
140
  });
141
141
  }
@@ -156,9 +156,12 @@ var HonoHttpServer = class {
156
156
  // src/hono-plugin.ts
157
157
  import { createHonoApp } from "@objectstack/hono";
158
158
  import { serveStatic as serveStatic2 } from "@hono/node-server/serve-static";
159
+ import * as fs from "fs";
160
+ import * as path from "path";
159
161
  var HonoServerPlugin = class {
160
162
  constructor(options = {}) {
161
163
  __publicField(this, "name", "com.objectstack.server.hono");
164
+ __publicField(this, "type", "server");
162
165
  __publicField(this, "version", "0.9.0");
163
166
  __publicField(this, "options");
164
167
  __publicField(this, "server");
@@ -192,25 +195,99 @@ var HonoServerPlugin = class {
192
195
  });
193
196
  ctx.logger.debug("Mounting ObjectStack Runtime App", { prefix: apiPath });
194
197
  this.server.mount("/", app);
198
+ const rawApp = this.server.getRawApp();
199
+ rawApp.get("/.well-known/objectstack", (c) => {
200
+ return c.redirect(apiPath);
201
+ });
202
+ ctx.logger.debug("Registered standard discovery endpoint", { path: "/.well-known/objectstack", target: apiPath });
195
203
  } catch (e) {
196
204
  ctx.logger.error("Failed to create standard Hono app", e);
197
205
  }
206
+ const mounts = this.options.staticMounts || [];
207
+ try {
208
+ const rawKernel = ctx.getKernel();
209
+ if (rawKernel.plugins) {
210
+ const loadedPlugins = rawKernel.plugins instanceof Map ? Array.from(rawKernel.plugins.values()) : Array.isArray(rawKernel.plugins) ? rawKernel.plugins : Object.values(rawKernel.plugins);
211
+ for (const plugin of loadedPlugins) {
212
+ if ((plugin.type === "ui" || plugin.type === "ui-plugin") && plugin.staticPath) {
213
+ const slug = plugin.slug || plugin.name.split("/").pop();
214
+ const baseRoute = `/${slug}`;
215
+ ctx.logger.debug(`Auto-mounting UI Plugin: ${plugin.name}`, {
216
+ path: baseRoute,
217
+ root: plugin.staticPath
218
+ });
219
+ mounts.push({
220
+ root: plugin.staticPath,
221
+ path: baseRoute,
222
+ rewrite: true,
223
+ // Strip prefix: /console/assets/x -> /assets/x
224
+ spa: true
225
+ });
226
+ if (plugin.default || plugin.isDefault) {
227
+ const rawApp = this.server.getRawApp();
228
+ rawApp.get("/", (c) => c.redirect(baseRoute));
229
+ ctx.logger.debug(`Set default UI redirect: / -> ${baseRoute}`);
230
+ }
231
+ }
232
+ }
233
+ }
234
+ } catch (err) {
235
+ ctx.logger.warn("Failed to auto-discover UI plugins", { error: err.message || err });
236
+ }
198
237
  if (this.options.staticRoot) {
238
+ mounts.push({
239
+ root: this.options.staticRoot,
240
+ path: "/",
241
+ rewrite: false,
242
+ spa: this.options.spaFallback
243
+ });
244
+ }
245
+ if (mounts.length > 0) {
199
246
  const rawApp = this.server.getRawApp();
200
- const staticRoot = this.options.staticRoot;
201
- ctx.logger.debug("Configuring static files", { root: staticRoot, spa: this.options.spaFallback });
202
- rawApp.get("/*", serveStatic2({ root: staticRoot }));
203
- if (this.options.spaFallback) {
204
- rawApp.get("*", async (c, next) => {
205
- const config = this.options.restConfig || {};
206
- const basePath = config.api?.basePath || "/api";
207
- if (c.req.path.startsWith(basePath)) {
208
- return next();
247
+ for (const mount of mounts) {
248
+ const mountRoot = path.resolve(process.cwd(), mount.root);
249
+ if (!fs.existsSync(mountRoot)) {
250
+ ctx.logger.warn(`Static mount root not found: ${mountRoot}. Skipping.`);
251
+ continue;
252
+ }
253
+ const mountPath = mount.path || "/";
254
+ const normalizedPath = mountPath.startsWith("/") ? mountPath : `/${mountPath}`;
255
+ const routePattern = normalizedPath === "/" ? "/*" : `${normalizedPath.replace(/\/$/, "")}/*`;
256
+ const routes = normalizedPath === "/" ? [routePattern] : [normalizedPath, routePattern];
257
+ ctx.logger.debug("Mounting static files", {
258
+ to: routes,
259
+ from: mountRoot,
260
+ rewrite: mount.rewrite,
261
+ spa: mount.spa
262
+ });
263
+ routes.forEach((route) => {
264
+ rawApp.get(
265
+ route,
266
+ serveStatic2({
267
+ root: mount.root,
268
+ rewriteRequestPath: (reqPath) => {
269
+ if (mount.rewrite && normalizedPath !== "/") {
270
+ if (reqPath.startsWith(normalizedPath)) {
271
+ return reqPath.substring(normalizedPath.length) || "/";
272
+ }
273
+ }
274
+ return reqPath;
275
+ }
276
+ })
277
+ );
278
+ if (mount.spa) {
279
+ rawApp.get(route, async (c, next) => {
280
+ const config = this.options.restConfig || {};
281
+ const basePath = config.api?.basePath || "/api";
282
+ if (c.req.path.startsWith(basePath)) {
283
+ return next();
284
+ }
285
+ return serveStatic2({
286
+ root: mount.root,
287
+ rewriteRequestPath: () => "index.html"
288
+ })(c, next);
289
+ });
209
290
  }
210
- return serveStatic2({
211
- root: staticRoot,
212
- rewriteRequestPath: () => "index.html"
213
- })(c, next);
214
291
  });
215
292
  }
216
293
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/hono-plugin.ts"],"sourcesContent":["export * from './hono-plugin';\nexport * from './adapter';\n\n","// Export IHttpServer from core\nexport * from '@objectstack/core';\n\nimport { \n IHttpServer, \n RouteHandler, \n Middleware \n} from '@objectstack/core';\nimport { Hono } from 'hono';\nimport { serve } from '@hono/node-server';\nimport { serveStatic } from '@hono/node-server/serve-static';\n\n/**\n * Hono Implementation of IHttpServer\n */\nexport class HonoHttpServer implements IHttpServer {\n private app: Hono;\n private server: any;\n private listeningPort: number | undefined;\n\n constructor(\n private port: number = 3000,\n private staticRoot?: string\n ) {\n this.app = new Hono();\n }\n\n // internal helper to convert standard handler to Hono handler\n private wrap(handler: RouteHandler) {\n return async (c: any) => {\n let body: any = {};\n \n // Try to parse JSON body first if content-type is JSON\n if (c.req.header('content-type')?.includes('application/json')) {\n try { \n body = await c.req.json(); \n } catch(e) {\n // If JSON parsing fails, try parseBody\n try { \n body = await c.req.parseBody(); \n } catch(e2) {}\n }\n } else {\n // For non-JSON content types, use parseBody\n try { \n body = await c.req.parseBody(); \n } catch(e) {}\n }\n \n const req = {\n params: c.req.param(),\n query: c.req.query(),\n body,\n headers: c.req.header(),\n method: c.req.method,\n path: c.req.path\n };\n\n let capturedResponse: any;\n\n const res = {\n json: (data: any) => { capturedResponse = c.json(data); },\n send: (data: string) => { capturedResponse = c.html(data); },\n status: (code: number) => { c.status(code); return res; },\n header: (name: string, value: string) => { c.header(name, value); return res; }\n };\n\n await handler(req as any, res as any);\n return capturedResponse;\n };\n }\n\n get(path: string, handler: RouteHandler) {\n this.app.get(path, this.wrap(handler));\n }\n post(path: string, handler: RouteHandler) {\n this.app.post(path, this.wrap(handler));\n }\n put(path: string, handler: RouteHandler) {\n this.app.put(path, this.wrap(handler));\n }\n delete(path: string, handler: RouteHandler) {\n this.app.delete(path, this.wrap(handler));\n }\n patch(path: string, handler: RouteHandler) {\n this.app.patch(path, this.wrap(handler));\n }\n \n use(pathOrHandler: string | Middleware, handler?: Middleware) {\n if (typeof pathOrHandler === 'string' && handler) {\n // Path based middleware\n // Hono middleware signature is different (c, next) => ...\n this.app.use(pathOrHandler, async (c, next) => {\n // Simplistic conversion\n await handler({} as any, {} as any, next);\n });\n } else if (typeof pathOrHandler === 'function') {\n // Global middleware\n this.app.use('*', async (c, next) => {\n await pathOrHandler({} as any, {} as any, next);\n });\n }\n }\n\n /**\n * Mount a sub-application or router\n */\n mount(path: string, subApp: Hono) {\n this.app.route(path, subApp);\n }\n\n\n async listen(port: number) {\n return new Promise<void>((resolve) => {\n if (this.staticRoot) {\n this.app.get('/*', serveStatic({ root: this.staticRoot }));\n }\n \n const targetPort = port || this.port;\n this.server = serve({\n fetch: this.app.fetch,\n port: targetPort\n }, (info) => {\n this.listeningPort = info.port;\n resolve();\n });\n });\n }\n\n getPort() {\n return this.listeningPort || this.port;\n }\n\n // Expose raw app for scenarios where standard interface is not enough\n getRawApp() {\n return this.app;\n }\n\n async close() {\n if (this.server && typeof this.server.close === 'function') {\n this.server.close();\n }\n }\n\n\n}\n","import { Plugin, PluginContext, IHttpServer, ApiRegistry } from '@objectstack/core';\nimport { ObjectStackProtocol } from '@objectstack/spec/api';\nimport { \n ApiRegistryEntryInput,\n ApiEndpointRegistrationInput,\n RestServerConfig,\n} from '@objectstack/spec/api';\nimport { HonoHttpServer } from './adapter';\nimport { createHonoApp } from '@objectstack/hono';\nimport { serveStatic } from '@hono/node-server/serve-static';\n\nexport interface HonoPluginOptions {\n port?: number;\n staticRoot?: string;\n /**\n * REST server configuration\n * Controls automatic endpoint generation and API behavior\n */\n restConfig?: RestServerConfig;\n /**\n * Whether to register standard ObjectStack CRUD endpoints\n * @default true\n */\n registerStandardEndpoints?: boolean;\n /**\n * Whether to load endpoints from API Registry\n * @default true\n */\n useApiRegistry?: boolean;\n\n /**\n * Whether to enable SPA fallback\n * If true, returns index.html for non-API 404s\n * @default false\n */\n spaFallback?: boolean;\n}\n\n/**\n * Hono Server Plugin\n * \n * Provides HTTP server capabilities using Hono framework.\n * Registers routes for ObjectStack Runtime Protocol.\n */\nexport class HonoServerPlugin implements Plugin {\n name = 'com.objectstack.server.hono';\n version = '0.9.0';\n \n // Constants\n private static readonly DEFAULT_ENDPOINT_PRIORITY = 100;\n private static readonly CORE_ENDPOINT_PRIORITY = 950;\n private static readonly DISCOVERY_ENDPOINT_PRIORITY = 900;\n \n private options: HonoPluginOptions;\n private server: HonoHttpServer;\n\n constructor(options: HonoPluginOptions = {}) {\n this.options = { \n port: 3000,\n registerStandardEndpoints: true,\n useApiRegistry: true,\n spaFallback: false,\n ...options\n };\n // We handle static root manually in start() to support SPA fallback\n this.server = new HonoHttpServer(this.options.port);\n }\n\n /**\n * Init phase - Setup HTTP server and register as service\n */\n init = async (ctx: PluginContext) => {\n ctx.logger.debug('Initializing Hono server plugin', { \n port: this.options.port,\n staticRoot: this.options.staticRoot \n });\n \n // Register HTTP server service as IHttpServer\n // Register as 'http.server' to match core requirements\n ctx.registerService('http.server', this.server);\n // Alias 'http-server' for backward compatibility\n ctx.registerService('http-server', this.server);\n ctx.logger.debug('HTTP server service registered', { serviceName: 'http.server' });\n }\n\n /**\n * Start phase - Bind routes and start listening\n */\n start = async (ctx: PluginContext) => {\n ctx.logger.debug('Starting Hono server plugin');\n \n // Use Standard ObjectStack Runtime Hono App\n try {\n const kernel = ctx.getKernel();\n const config = this.options.restConfig || {};\n // Calculate prefix similar to before\n const apiVersion = config.api?.version || 'v1';\n const basePath = config.api?.basePath || '/api';\n const apiPath = config.api?.apiPath || `${basePath}/${apiVersion}`;\n \n const app = createHonoApp({ \n kernel,\n prefix: apiPath // Use the calculated path\n });\n \n ctx.logger.debug('Mounting ObjectStack Runtime App', { prefix: apiPath });\n // Use the mount method we added to HonoHttpServer\n this.server.mount('/', app as any);\n\n } catch (e: any) {\n ctx.logger.error('Failed to create standard Hono app', e);\n }\n\n // Configure Static Files & SPA Fallback\n if (this.options.staticRoot) {\n const rawApp = this.server.getRawApp();\n const staticRoot = this.options.staticRoot;\n \n ctx.logger.debug('Configuring static files', { root: staticRoot, spa: this.options.spaFallback });\n \n // 1. Static Files\n rawApp.get('/*', serveStatic({ root: staticRoot }));\n \n // 2. SPA Fallback\n if (this.options.spaFallback) {\n rawApp.get('*', async (c, next) => {\n // Skip API paths\n const config = this.options.restConfig || {};\n const basePath = config.api?.basePath || '/api';\n \n if (c.req.path.startsWith(basePath)) {\n return next();\n }\n \n // Fallback to index.html\n return serveStatic({ \n root: staticRoot,\n rewriteRequestPath: () => 'index.html'\n })(c, next);\n });\n }\n }\n\n // Start server on kernel:ready hook\n ctx.hook('kernel:ready', async () => {\n const port = this.options.port || 3000;\n ctx.logger.debug('Starting HTTP server', { port });\n \n await this.server.listen(port);\n \n const actualPort = this.server.getPort();\n ctx.logger.info('HTTP server started successfully', { \n port: actualPort, \n url: `http://localhost:${actualPort}` \n });\n });\n }\n\n /**\n * Destroy phase - Stop server\n */\n async destroy() {\n this.server.close();\n // Note: Can't use ctx.logger here since we're in destroy\n console.log('[HonoServerPlugin] Server stopped');\n }\n}\n"],"mappings":"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","names":["serveStatic","serveStatic"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/hono-plugin.ts"],"sourcesContent":["export * from './hono-plugin';\nexport * from './adapter';\n\n","// Export IHttpServer from core\nexport * from '@objectstack/core';\n\nimport { \n IHttpServer, \n RouteHandler, \n Middleware \n} from '@objectstack/core';\nimport { Hono } from 'hono';\nimport { serve } from '@hono/node-server';\nimport { serveStatic } from '@hono/node-server/serve-static';\n\n/**\n * Hono Implementation of IHttpServer\n */\nexport class HonoHttpServer implements IHttpServer {\n private app: Hono;\n private server: any;\n private listeningPort: number | undefined;\n\n constructor(\n private port: number = 3000,\n private staticRoot?: string\n ) {\n this.app = new Hono();\n }\n\n // internal helper to convert standard handler to Hono handler\n private wrap(handler: RouteHandler) {\n return async (c: any) => {\n let body: any = {};\n \n // Try to parse JSON body first if content-type is JSON\n if (c.req.header('content-type')?.includes('application/json')) {\n try { \n body = await c.req.json(); \n } catch(e) {\n // If JSON parsing fails, try parseBody\n try { \n body = await c.req.parseBody(); \n } catch(e2) {}\n }\n } else {\n // For non-JSON content types, use parseBody\n try { \n body = await c.req.parseBody(); \n } catch(e) {}\n }\n \n const req = {\n params: c.req.param(),\n query: c.req.query(),\n body,\n headers: c.req.header(),\n method: c.req.method,\n path: c.req.path\n };\n\n let capturedResponse: any;\n\n const res = {\n json: (data: any) => { capturedResponse = c.json(data); },\n send: (data: string) => { capturedResponse = c.html(data); },\n status: (code: number) => { c.status(code); return res; },\n header: (name: string, value: string) => { c.header(name, value); return res; }\n };\n\n await handler(req as any, res as any);\n return capturedResponse;\n };\n }\n\n get(path: string, handler: RouteHandler) {\n this.app.get(path, this.wrap(handler));\n }\n post(path: string, handler: RouteHandler) {\n this.app.post(path, this.wrap(handler));\n }\n put(path: string, handler: RouteHandler) {\n this.app.put(path, this.wrap(handler));\n }\n delete(path: string, handler: RouteHandler) {\n this.app.delete(path, this.wrap(handler));\n }\n patch(path: string, handler: RouteHandler) {\n this.app.patch(path, this.wrap(handler));\n }\n \n use(pathOrHandler: string | Middleware, handler?: Middleware) {\n if (typeof pathOrHandler === 'string' && handler) {\n // Path based middleware\n // Hono middleware signature is different (c, next) => ...\n this.app.use(pathOrHandler, async (c, next) => {\n // Simplistic conversion\n await handler({} as any, {} as any, next);\n });\n } else if (typeof pathOrHandler === 'function') {\n // Global middleware\n this.app.use('*', async (c, next) => {\n await pathOrHandler({} as any, {} as any, next);\n });\n }\n }\n\n /**\n * Mount a sub-application or router\n */\n mount(path: string, subApp: Hono) {\n this.app.route(path, subApp);\n }\n\n\n async listen(port: number) {\n return new Promise<void>((resolve) => {\n if (this.staticRoot) {\n this.app.get('/*', serveStatic({ root: this.staticRoot }));\n }\n \n const targetPort = port || this.port;\n this.server = serve({\n fetch: this.app.fetch,\n port: targetPort\n }, (info) => {\n this.listeningPort = info.port;\n resolve();\n });\n });\n }\n\n getPort() {\n return this.listeningPort || this.port;\n }\n\n // Expose raw app for scenarios where standard interface is not enough\n getRawApp() {\n return this.app;\n }\n\n async close() {\n if (this.server && typeof this.server.close === 'function') {\n this.server.close();\n }\n }\n\n\n}\n","import { Plugin, PluginContext, IHttpServer, ApiRegistry } from '@objectstack/core';\nimport { ObjectStackProtocol } from '@objectstack/spec/api';\nimport { \n ApiRegistryEntryInput,\n ApiEndpointRegistrationInput,\n RestServerConfig,\n} from '@objectstack/spec/api';\nimport { HonoHttpServer } from './adapter';\nimport { createHonoApp } from '@objectstack/hono';\nimport { serveStatic } from '@hono/node-server/serve-static';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface StaticMount {\n root: string;\n path?: string;\n rewrite?: boolean;\n spa?: boolean;\n}\n\nexport interface HonoPluginOptions {\n port?: number;\n staticRoot?: string;\n /**\n * Multiple static resource mounts\n */\n staticMounts?: StaticMount[];\n /**\n * REST server configuration\n * Controls automatic endpoint generation and API behavior\n */\n restConfig?: RestServerConfig;\n /**\n * Whether to register standard ObjectStack CRUD endpoints\n * @default true\n */\n registerStandardEndpoints?: boolean;\n /**\n * Whether to load endpoints from API Registry\n * @default true\n */\n useApiRegistry?: boolean;\n\n /**\n * Whether to enable SPA fallback\n * If true, returns index.html for non-API 404s\n * @default false\n */\n spaFallback?: boolean;\n}\n\n/**\n * Hono Server Plugin\n * \n * Provides HTTP server capabilities using Hono framework.\n * Registers routes for ObjectStack Runtime Protocol.\n */\nexport class HonoServerPlugin implements Plugin {\n name = 'com.objectstack.server.hono';\n type = 'server';\n version = '0.9.0';\n \n // Constants\n private static readonly DEFAULT_ENDPOINT_PRIORITY = 100;\n private static readonly CORE_ENDPOINT_PRIORITY = 950;\n private static readonly DISCOVERY_ENDPOINT_PRIORITY = 900;\n \n private options: HonoPluginOptions;\n private server: HonoHttpServer;\n\n constructor(options: HonoPluginOptions = {}) {\n this.options = { \n port: 3000,\n registerStandardEndpoints: true,\n useApiRegistry: true,\n spaFallback: false,\n ...options\n };\n // We handle static root manually in start() to support SPA fallback\n this.server = new HonoHttpServer(this.options.port);\n }\n\n /**\n * Init phase - Setup HTTP server and register as service\n */\n init = async (ctx: PluginContext) => {\n ctx.logger.debug('Initializing Hono server plugin', { \n port: this.options.port,\n staticRoot: this.options.staticRoot \n });\n \n // Register HTTP server service as IHttpServer\n // Register as 'http.server' to match core requirements\n ctx.registerService('http.server', this.server);\n // Alias 'http-server' for backward compatibility\n ctx.registerService('http-server', this.server);\n ctx.logger.debug('HTTP server service registered', { serviceName: 'http.server' });\n }\n\n /**\n * Start phase - Bind routes and start listening\n */\n start = async (ctx: PluginContext) => {\n ctx.logger.debug('Starting Hono server plugin');\n \n // Use Standard ObjectStack Runtime Hono App\n try {\n const kernel = ctx.getKernel();\n const config = this.options.restConfig || {};\n // Calculate prefix similar to before\n const apiVersion = config.api?.version || 'v1';\n const basePath = config.api?.basePath || '/api';\n const apiPath = config.api?.apiPath || `${basePath}/${apiVersion}`;\n \n const app = createHonoApp({ \n kernel,\n prefix: apiPath // Use the calculated path\n });\n \n ctx.logger.debug('Mounting ObjectStack Runtime App', { prefix: apiPath });\n // Use the mount method we added to HonoHttpServer\n this.server.mount('/', app as any);\n\n // Register Standard Discovery Endpoint\n const rawApp = this.server.getRawApp();\n rawApp.get('/.well-known/objectstack', (c) => {\n return c.redirect(apiPath);\n });\n ctx.logger.debug('Registered standard discovery endpoint', { path: '/.well-known/objectstack', target: apiPath });\n\n } catch (e: any) {\n ctx.logger.error('Failed to create standard Hono app', e);\n }\n\n // Configure Static Files & SPA Fallback\n const mounts: StaticMount[] = this.options.staticMounts || [];\n\n // Auto-discover UI Plugins\n try {\n const rawKernel = ctx.getKernel() as any;\n if (rawKernel.plugins) {\n const loadedPlugins = rawKernel.plugins instanceof Map \n ? Array.from(rawKernel.plugins.values()) \n : Array.isArray(rawKernel.plugins) ? rawKernel.plugins : Object.values(rawKernel.plugins);\n\n for (const plugin of (loadedPlugins as any[])) {\n // Check for UI Plugin signature\n // Support legacy 'ui-plugin' and new 'ui' type\n if ((plugin.type === 'ui' || plugin.type === 'ui-plugin') && plugin.staticPath) {\n // Derive base route from name: @org/console -> console\n const slug = plugin.slug || plugin.name.split('/').pop();\n const baseRoute = `/${slug}`;\n \n ctx.logger.debug(`Auto-mounting UI Plugin: ${plugin.name}`, { \n path: baseRoute, \n root: plugin.staticPath \n });\n\n mounts.push({\n root: plugin.staticPath,\n path: baseRoute,\n rewrite: true, // Strip prefix: /console/assets/x -> /assets/x\n spa: true\n });\n\n // Handle Default Plugin Redirect\n if (plugin.default || plugin.isDefault) {\n const rawApp = this.server.getRawApp();\n rawApp.get('/', (c) => c.redirect(baseRoute));\n ctx.logger.debug(`Set default UI redirect: / -> ${baseRoute}`);\n }\n }\n }\n }\n } catch (err: any) {\n ctx.logger.warn('Failed to auto-discover UI plugins', { error: err.message || err });\n }\n\n // Backward compatibility for staticRoot\n if (this.options.staticRoot) {\n mounts.push({\n root: this.options.staticRoot,\n path: '/',\n rewrite: false,\n spa: this.options.spaFallback\n });\n }\n\n if (mounts.length > 0) {\n const rawApp = this.server.getRawApp();\n \n for (const mount of mounts) {\n const mountRoot = path.resolve(process.cwd(), mount.root);\n\n if (!fs.existsSync(mountRoot)) {\n ctx.logger.warn(`Static mount root not found: ${mountRoot}. Skipping.`);\n continue;\n }\n\n const mountPath = mount.path || '/';\n const normalizedPath = mountPath.startsWith('/') ? mountPath : `/${mountPath}`;\n const routePattern = normalizedPath === '/' ? '/*' : `${normalizedPath.replace(/\\/$/, '')}/*`;\n \n // Routes to register: both /mount and /mount/*\n const routes = normalizedPath === '/' ? [routePattern] : [normalizedPath, routePattern];\n\n ctx.logger.debug('Mounting static files', { \n to: routes, \n from: mountRoot, \n rewrite: mount.rewrite, \n spa: mount.spa \n });\n\n routes.forEach(route => {\n // 1. Serve Static Files\n rawApp.get(\n route, \n serveStatic({ \n root: mount.root,\n rewriteRequestPath: (reqPath) => {\n if (mount.rewrite && normalizedPath !== '/') {\n // /console/assets/style.css -> /assets/style.css\n if (reqPath.startsWith(normalizedPath)) {\n return reqPath.substring(normalizedPath.length) || '/';\n }\n }\n return reqPath;\n }\n })\n );\n\n // 2. SPA Fallback (Scoped)\n if (mount.spa) {\n rawApp.get(route, async (c, next) => {\n // Skip if API path check\n const config = this.options.restConfig || {};\n const basePath = config.api?.basePath || '/api';\n \n if (c.req.path.startsWith(basePath)) {\n return next();\n }\n\n return serveStatic({ \n root: mount.root,\n rewriteRequestPath: () => 'index.html'\n })(c, next);\n });\n }\n });\n }\n }\n\n // Start server on kernel:ready hook\n ctx.hook('kernel:ready', async () => {\n const port = this.options.port || 3000;\n ctx.logger.debug('Starting HTTP server', { port });\n \n await this.server.listen(port);\n \n const actualPort = this.server.getPort();\n ctx.logger.info('HTTP server started successfully', { \n port: actualPort, \n url: `http://localhost:${actualPort}` \n });\n });\n }\n\n /**\n * Destroy phase - Stop server\n */\n async destroy() {\n this.server.close();\n // Note: Can't use ctx.logger here since we're in destroy\n console.log('[HonoServerPlugin] Server stopped');\n }\n}\n"],"mappings":"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","names":["path","resolve","serveStatic","serveStatic"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/plugin-hono-server",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Standard Hono Server Adapter for ObjectStack Runtime",
6
6
  "main": "dist/index.js",
@@ -8,11 +8,11 @@
8
8
  "dependencies": {
9
9
  "@hono/node-server": "^1.2.0",
10
10
  "hono": "^4.0.0",
11
- "@objectstack/core": "1.0.5",
12
- "@objectstack/hono": "1.0.5",
13
- "@objectstack/runtime": "1.0.5",
14
- "@objectstack/spec": "1.0.5",
15
- "@objectstack/types": "1.0.5"
11
+ "@objectstack/core": "1.0.7",
12
+ "@objectstack/hono": "1.0.7",
13
+ "@objectstack/runtime": "1.0.7",
14
+ "@objectstack/spec": "1.0.7",
15
+ "@objectstack/types": "1.0.7"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^25.1.0",
@@ -4,6 +4,14 @@ import { PluginContext } from '@objectstack/core';
4
4
  import { createHonoApp } from '@objectstack/hono';
5
5
  import { HonoHttpServer } from './adapter';
6
6
 
7
+ vi.mock('fs', async (importOriginal) => {
8
+ const actual = await importOriginal<typeof import('fs')>();
9
+ return {
10
+ ...actual,
11
+ existsSync: vi.fn().mockReturnValue(true)
12
+ };
13
+ });
14
+
7
15
  // Mock dependencies
8
16
  vi.mock('@objectstack/hono', () => ({
9
17
  createHonoApp: vi.fn(),
@@ -131,6 +139,6 @@ describe('HonoServerPlugin', () => {
131
139
  // Should register static files middleware
132
140
  expect(rawApp.get).toHaveBeenCalledWith('/*', expect.anything());
133
141
  // Should register SPA fallback middleware
134
- expect(rawApp.get).toHaveBeenCalledWith('*', expect.anything());
142
+ expect(rawApp.get).toHaveBeenCalledWith('/*', expect.anything());
135
143
  });
136
144
  });
@@ -8,10 +8,23 @@ import {
8
8
  import { HonoHttpServer } from './adapter';
9
9
  import { createHonoApp } from '@objectstack/hono';
10
10
  import { serveStatic } from '@hono/node-server/serve-static';
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+
14
+ export interface StaticMount {
15
+ root: string;
16
+ path?: string;
17
+ rewrite?: boolean;
18
+ spa?: boolean;
19
+ }
11
20
 
12
21
  export interface HonoPluginOptions {
13
22
  port?: number;
14
23
  staticRoot?: string;
24
+ /**
25
+ * Multiple static resource mounts
26
+ */
27
+ staticMounts?: StaticMount[];
15
28
  /**
16
29
  * REST server configuration
17
30
  * Controls automatic endpoint generation and API behavior
@@ -44,6 +57,7 @@ export interface HonoPluginOptions {
44
57
  */
45
58
  export class HonoServerPlugin implements Plugin {
46
59
  name = 'com.objectstack.server.hono';
60
+ type = 'server';
47
61
  version = '0.9.0';
48
62
 
49
63
  // Constants
@@ -107,36 +121,131 @@ export class HonoServerPlugin implements Plugin {
107
121
  // Use the mount method we added to HonoHttpServer
108
122
  this.server.mount('/', app as any);
109
123
 
124
+ // Register Standard Discovery Endpoint
125
+ const rawApp = this.server.getRawApp();
126
+ rawApp.get('/.well-known/objectstack', (c) => {
127
+ return c.redirect(apiPath);
128
+ });
129
+ ctx.logger.debug('Registered standard discovery endpoint', { path: '/.well-known/objectstack', target: apiPath });
130
+
110
131
  } catch (e: any) {
111
132
  ctx.logger.error('Failed to create standard Hono app', e);
112
133
  }
113
134
 
114
135
  // Configure Static Files & SPA Fallback
136
+ const mounts: StaticMount[] = this.options.staticMounts || [];
137
+
138
+ // Auto-discover UI Plugins
139
+ try {
140
+ const rawKernel = ctx.getKernel() as any;
141
+ if (rawKernel.plugins) {
142
+ const loadedPlugins = rawKernel.plugins instanceof Map
143
+ ? Array.from(rawKernel.plugins.values())
144
+ : Array.isArray(rawKernel.plugins) ? rawKernel.plugins : Object.values(rawKernel.plugins);
145
+
146
+ for (const plugin of (loadedPlugins as any[])) {
147
+ // Check for UI Plugin signature
148
+ // Support legacy 'ui-plugin' and new 'ui' type
149
+ if ((plugin.type === 'ui' || plugin.type === 'ui-plugin') && plugin.staticPath) {
150
+ // Derive base route from name: @org/console -> console
151
+ const slug = plugin.slug || plugin.name.split('/').pop();
152
+ const baseRoute = `/${slug}`;
153
+
154
+ ctx.logger.debug(`Auto-mounting UI Plugin: ${plugin.name}`, {
155
+ path: baseRoute,
156
+ root: plugin.staticPath
157
+ });
158
+
159
+ mounts.push({
160
+ root: plugin.staticPath,
161
+ path: baseRoute,
162
+ rewrite: true, // Strip prefix: /console/assets/x -> /assets/x
163
+ spa: true
164
+ });
165
+
166
+ // Handle Default Plugin Redirect
167
+ if (plugin.default || plugin.isDefault) {
168
+ const rawApp = this.server.getRawApp();
169
+ rawApp.get('/', (c) => c.redirect(baseRoute));
170
+ ctx.logger.debug(`Set default UI redirect: / -> ${baseRoute}`);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ } catch (err: any) {
176
+ ctx.logger.warn('Failed to auto-discover UI plugins', { error: err.message || err });
177
+ }
178
+
179
+ // Backward compatibility for staticRoot
115
180
  if (this.options.staticRoot) {
181
+ mounts.push({
182
+ root: this.options.staticRoot,
183
+ path: '/',
184
+ rewrite: false,
185
+ spa: this.options.spaFallback
186
+ });
187
+ }
188
+
189
+ if (mounts.length > 0) {
116
190
  const rawApp = this.server.getRawApp();
117
- const staticRoot = this.options.staticRoot;
118
-
119
- ctx.logger.debug('Configuring static files', { root: staticRoot, spa: this.options.spaFallback });
120
-
121
- // 1. Static Files
122
- rawApp.get('/*', serveStatic({ root: staticRoot }));
123
191
 
124
- // 2. SPA Fallback
125
- if (this.options.spaFallback) {
126
- rawApp.get('*', async (c, next) => {
127
- // Skip API paths
128
- const config = this.options.restConfig || {};
129
- const basePath = config.api?.basePath || '/api';
130
-
131
- if (c.req.path.startsWith(basePath)) {
132
- return next();
192
+ for (const mount of mounts) {
193
+ const mountRoot = path.resolve(process.cwd(), mount.root);
194
+
195
+ if (!fs.existsSync(mountRoot)) {
196
+ ctx.logger.warn(`Static mount root not found: ${mountRoot}. Skipping.`);
197
+ continue;
198
+ }
199
+
200
+ const mountPath = mount.path || '/';
201
+ const normalizedPath = mountPath.startsWith('/') ? mountPath : `/${mountPath}`;
202
+ const routePattern = normalizedPath === '/' ? '/*' : `${normalizedPath.replace(/\/$/, '')}/*`;
203
+
204
+ // Routes to register: both /mount and /mount/*
205
+ const routes = normalizedPath === '/' ? [routePattern] : [normalizedPath, routePattern];
206
+
207
+ ctx.logger.debug('Mounting static files', {
208
+ to: routes,
209
+ from: mountRoot,
210
+ rewrite: mount.rewrite,
211
+ spa: mount.spa
212
+ });
213
+
214
+ routes.forEach(route => {
215
+ // 1. Serve Static Files
216
+ rawApp.get(
217
+ route,
218
+ serveStatic({
219
+ root: mount.root,
220
+ rewriteRequestPath: (reqPath) => {
221
+ if (mount.rewrite && normalizedPath !== '/') {
222
+ // /console/assets/style.css -> /assets/style.css
223
+ if (reqPath.startsWith(normalizedPath)) {
224
+ return reqPath.substring(normalizedPath.length) || '/';
225
+ }
226
+ }
227
+ return reqPath;
228
+ }
229
+ })
230
+ );
231
+
232
+ // 2. SPA Fallback (Scoped)
233
+ if (mount.spa) {
234
+ rawApp.get(route, async (c, next) => {
235
+ // Skip if API path check
236
+ const config = this.options.restConfig || {};
237
+ const basePath = config.api?.basePath || '/api';
238
+
239
+ if (c.req.path.startsWith(basePath)) {
240
+ return next();
241
+ }
242
+
243
+ return serveStatic({
244
+ root: mount.root,
245
+ rewriteRequestPath: () => 'index.html'
246
+ })(c, next);
247
+ });
133
248
  }
134
-
135
- // Fallback to index.html
136
- return serveStatic({
137
- root: staticRoot,
138
- rewriteRequestPath: () => 'index.html'
139
- })(c, next);
140
249
  });
141
250
  }
142
251
  }