@objectstack/plugin-hono-server 1.0.5 → 1.0.6

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.6 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 9.43 KB
14
+ ESM dist/index.mjs.map 17.97 KB
15
+ ESM ⚡️ Build success in 22ms
16
+ CJS dist/index.js 10.38 KB
17
+ CJS dist/index.js.map 17.98 KB
18
+ CJS ⚡️ Build success in 22ms
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 3388ms
21
+ DTS dist/index.d.mts 2.83 KB
22
+ DTS dist/index.d.ts 2.83 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @objectstack/plugin-hono-server
2
2
 
3
+ ## 1.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [a7f7b9d]
8
+ - @objectstack/spec@1.0.6
9
+ - @objectstack/core@1.0.6
10
+ - @objectstack/runtime@1.0.6
11
+ - @objectstack/types@1.0.6
12
+ - @objectstack/hono@1.0.6
13
+
3
14
  ## 1.0.5
4
15
 
5
16
  ### 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
@@ -85,4 +95,4 @@ declare class HonoHttpServer implements IHttpServer {
85
95
  close(): Promise<void>;
86
96
  }
87
97
 
88
- export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin };
98
+ 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
@@ -85,4 +95,4 @@ declare class HonoHttpServer implements IHttpServer {
85
95
  close(): Promise<void>;
86
96
  }
87
97
 
88
- export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin };
98
+ 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,6 +168,8 @@ 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");
@@ -197,22 +209,61 @@ var HonoServerPlugin = class {
197
209
  } catch (e) {
198
210
  ctx.logger.error("Failed to create standard Hono app", e);
199
211
  }
212
+ const mounts = this.options.staticMounts || [];
200
213
  if (this.options.staticRoot) {
214
+ mounts.push({
215
+ root: this.options.staticRoot,
216
+ path: "/",
217
+ rewrite: false,
218
+ spa: this.options.spaFallback
219
+ });
220
+ }
221
+ if (mounts.length > 0) {
201
222
  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();
223
+ for (const mount of mounts) {
224
+ const mountRoot = path.resolve(process.cwd(), mount.root);
225
+ if (!fs.existsSync(mountRoot)) {
226
+ ctx.logger.warn(`Static mount root not found: ${mountRoot}. Skipping.`);
227
+ continue;
228
+ }
229
+ const mountPath = mount.path || "/";
230
+ const normalizedPath = mountPath.startsWith("/") ? mountPath : `/${mountPath}`;
231
+ const routePattern = normalizedPath === "/" ? "/*" : `${normalizedPath.replace(/\/$/, "")}/*`;
232
+ const routes = normalizedPath === "/" ? [routePattern] : [normalizedPath, routePattern];
233
+ ctx.logger.debug("Mounting static files", {
234
+ to: routes,
235
+ from: mountRoot,
236
+ rewrite: mount.rewrite,
237
+ spa: mount.spa
238
+ });
239
+ routes.forEach((route) => {
240
+ rawApp.get(
241
+ route,
242
+ (0, import_serve_static2.serveStatic)({
243
+ root: mount.root,
244
+ rewriteRequestPath: (reqPath) => {
245
+ if (mount.rewrite && normalizedPath !== "/") {
246
+ if (reqPath.startsWith(normalizedPath)) {
247
+ return reqPath.substring(normalizedPath.length) || "/";
248
+ }
249
+ }
250
+ return reqPath;
251
+ }
252
+ })
253
+ );
254
+ if (mount.spa) {
255
+ rawApp.get(route, async (c, next) => {
256
+ const config = this.options.restConfig || {};
257
+ const basePath = config.api?.basePath || "/api";
258
+ if (c.req.path.startsWith(basePath)) {
259
+ return next();
260
+ }
261
+ return (0, import_serve_static2.serveStatic)({
262
+ root: mount.root,
263
+ rewriteRequestPath: () => "index.html"
264
+ })(c, next);
265
+ });
211
266
  }
212
- return (0, import_serve_static2.serveStatic)({
213
- root: staticRoot,
214
- rewriteRequestPath: () => "index.html"
215
- })(c, next);
216
267
  });
217
268
  }
218
269
  }
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 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 const mounts: StaticMount[] = this.options.staticMounts || [];\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,6 +156,8 @@ 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");
@@ -195,22 +197,61 @@ var HonoServerPlugin = class {
195
197
  } catch (e) {
196
198
  ctx.logger.error("Failed to create standard Hono app", e);
197
199
  }
200
+ const mounts = this.options.staticMounts || [];
198
201
  if (this.options.staticRoot) {
202
+ mounts.push({
203
+ root: this.options.staticRoot,
204
+ path: "/",
205
+ rewrite: false,
206
+ spa: this.options.spaFallback
207
+ });
208
+ }
209
+ if (mounts.length > 0) {
199
210
  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();
211
+ for (const mount of mounts) {
212
+ const mountRoot = path.resolve(process.cwd(), mount.root);
213
+ if (!fs.existsSync(mountRoot)) {
214
+ ctx.logger.warn(`Static mount root not found: ${mountRoot}. Skipping.`);
215
+ continue;
216
+ }
217
+ const mountPath = mount.path || "/";
218
+ const normalizedPath = mountPath.startsWith("/") ? mountPath : `/${mountPath}`;
219
+ const routePattern = normalizedPath === "/" ? "/*" : `${normalizedPath.replace(/\/$/, "")}/*`;
220
+ const routes = normalizedPath === "/" ? [routePattern] : [normalizedPath, routePattern];
221
+ ctx.logger.debug("Mounting static files", {
222
+ to: routes,
223
+ from: mountRoot,
224
+ rewrite: mount.rewrite,
225
+ spa: mount.spa
226
+ });
227
+ routes.forEach((route) => {
228
+ rawApp.get(
229
+ route,
230
+ serveStatic2({
231
+ root: mount.root,
232
+ rewriteRequestPath: (reqPath) => {
233
+ if (mount.rewrite && normalizedPath !== "/") {
234
+ if (reqPath.startsWith(normalizedPath)) {
235
+ return reqPath.substring(normalizedPath.length) || "/";
236
+ }
237
+ }
238
+ return reqPath;
239
+ }
240
+ })
241
+ );
242
+ if (mount.spa) {
243
+ rawApp.get(route, async (c, next) => {
244
+ const config = this.options.restConfig || {};
245
+ const basePath = config.api?.basePath || "/api";
246
+ if (c.req.path.startsWith(basePath)) {
247
+ return next();
248
+ }
249
+ return serveStatic2({
250
+ root: mount.root,
251
+ rewriteRequestPath: () => "index.html"
252
+ })(c, next);
253
+ });
209
254
  }
210
- return serveStatic2({
211
- root: staticRoot,
212
- rewriteRequestPath: () => "index.html"
213
- })(c, next);
214
255
  });
215
256
  }
216
257
  }
@@ -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 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 const mounts: StaticMount[] = this.options.staticMounts || [];\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.6",
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.6",
12
+ "@objectstack/runtime": "1.0.6",
13
+ "@objectstack/hono": "1.0.6",
14
+ "@objectstack/spec": "1.0.6",
15
+ "@objectstack/types": "1.0.6"
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
@@ -112,31 +125,78 @@ export class HonoServerPlugin implements Plugin {
112
125
  }
113
126
 
114
127
  // Configure Static Files & SPA Fallback
128
+ const mounts: StaticMount[] = this.options.staticMounts || [];
129
+
130
+ // Backward compatibility for staticRoot
115
131
  if (this.options.staticRoot) {
132
+ mounts.push({
133
+ root: this.options.staticRoot,
134
+ path: '/',
135
+ rewrite: false,
136
+ spa: this.options.spaFallback
137
+ });
138
+ }
139
+
140
+ if (mounts.length > 0) {
116
141
  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
142
 
121
- // 1. Static Files
122
- rawApp.get('/*', serveStatic({ root: staticRoot }));
123
-
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();
143
+ for (const mount of mounts) {
144
+ const mountRoot = path.resolve(process.cwd(), mount.root);
145
+
146
+ if (!fs.existsSync(mountRoot)) {
147
+ ctx.logger.warn(`Static mount root not found: ${mountRoot}. Skipping.`);
148
+ continue;
149
+ }
150
+
151
+ const mountPath = mount.path || '/';
152
+ const normalizedPath = mountPath.startsWith('/') ? mountPath : `/${mountPath}`;
153
+ const routePattern = normalizedPath === '/' ? '/*' : `${normalizedPath.replace(/\/$/, '')}/*`;
154
+
155
+ // Routes to register: both /mount and /mount/*
156
+ const routes = normalizedPath === '/' ? [routePattern] : [normalizedPath, routePattern];
157
+
158
+ ctx.logger.debug('Mounting static files', {
159
+ to: routes,
160
+ from: mountRoot,
161
+ rewrite: mount.rewrite,
162
+ spa: mount.spa
163
+ });
164
+
165
+ routes.forEach(route => {
166
+ // 1. Serve Static Files
167
+ rawApp.get(
168
+ route,
169
+ serveStatic({
170
+ root: mount.root,
171
+ rewriteRequestPath: (reqPath) => {
172
+ if (mount.rewrite && normalizedPath !== '/') {
173
+ // /console/assets/style.css -> /assets/style.css
174
+ if (reqPath.startsWith(normalizedPath)) {
175
+ return reqPath.substring(normalizedPath.length) || '/';
176
+ }
177
+ }
178
+ return reqPath;
179
+ }
180
+ })
181
+ );
182
+
183
+ // 2. SPA Fallback (Scoped)
184
+ if (mount.spa) {
185
+ rawApp.get(route, async (c, next) => {
186
+ // Skip if API path check
187
+ const config = this.options.restConfig || {};
188
+ const basePath = config.api?.basePath || '/api';
189
+
190
+ if (c.req.path.startsWith(basePath)) {
191
+ return next();
192
+ }
193
+
194
+ return serveStatic({
195
+ root: mount.root,
196
+ rewriteRequestPath: () => 'index.html'
197
+ })(c, next);
198
+ });
133
199
  }
134
-
135
- // Fallback to index.html
136
- return serveStatic({
137
- root: staticRoot,
138
- rewriteRequestPath: () => 'index.html'
139
- })(c, next);
140
200
  });
141
201
  }
142
202
  }