@objectstack/plugin-hono-server 0.4.2 → 0.6.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @objectstack/plugin-hono-server
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Patch release for maintenance and stability improvements
8
+ - Updated dependencies
9
+ - @objectstack/spec@0.6.1
10
+ - @objectstack/types@0.6.1
11
+ - @objectstack/core@0.6.1
12
+
13
+ ## 0.6.0
14
+
15
+ ### Minor Changes
16
+
17
+ - b2df5f7: Unified version bump to 0.5.0
18
+
19
+ - Standardized all package versions to 0.5.0 across the monorepo
20
+ - Fixed driver-memory package.json paths for proper module resolution
21
+ - Ensured all packages are in sync for the 0.5.0 release
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [b2df5f7]
26
+ - @objectstack/spec@0.6.0
27
+ - @objectstack/types@0.6.0
28
+ - @objectstack/core@0.6.0
29
+
3
30
  ## 0.4.2
4
31
 
5
32
  ### Patch Changes
@@ -0,0 +1,23 @@
1
+ export * from '@objectstack/core';
2
+ import { IHttpServer, RouteHandler, Middleware } from '@objectstack/core';
3
+ import { Hono } from 'hono';
4
+ /**
5
+ * Hono Implementation of IHttpServer
6
+ */
7
+ export declare class HonoHttpServer implements IHttpServer {
8
+ private port;
9
+ private staticRoot?;
10
+ private app;
11
+ private server;
12
+ constructor(port?: number, staticRoot?: string | undefined);
13
+ private wrap;
14
+ get(path: string, handler: RouteHandler): void;
15
+ post(path: string, handler: RouteHandler): void;
16
+ put(path: string, handler: RouteHandler): void;
17
+ delete(path: string, handler: RouteHandler): void;
18
+ patch(path: string, handler: RouteHandler): void;
19
+ use(pathOrHandler: string | Middleware, handler?: Middleware): void;
20
+ listen(port: number): Promise<void>;
21
+ getRawApp(): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
22
+ close(): Promise<void>;
23
+ }
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.HonoHttpServer = void 0;
18
+ // Export IHttpServer from core
19
+ __exportStar(require("@objectstack/core"), exports);
20
+ const hono_1 = require("hono");
21
+ const node_server_1 = require("@hono/node-server");
22
+ const serve_static_1 = require("@hono/node-server/serve-static");
23
+ /**
24
+ * Hono Implementation of IHttpServer
25
+ */
26
+ class HonoHttpServer {
27
+ port;
28
+ staticRoot;
29
+ app;
30
+ server;
31
+ constructor(port = 3000, staticRoot) {
32
+ this.port = port;
33
+ this.staticRoot = staticRoot;
34
+ this.app = new hono_1.Hono();
35
+ }
36
+ // internal helper to convert standard handler to Hono handler
37
+ wrap(handler) {
38
+ return async (c) => {
39
+ const req = {
40
+ params: c.req.param(),
41
+ query: c.req.query(),
42
+ body: await c.req.parseBody().catch(() => { }), // fallback
43
+ headers: c.req.header(),
44
+ method: c.req.method,
45
+ path: c.req.path
46
+ };
47
+ // Try to parse JSON body if possible
48
+ if (c.req.header('content-type')?.includes('application/json')) {
49
+ try {
50
+ req.body = await c.req.json();
51
+ }
52
+ catch (e) { }
53
+ }
54
+ let capturedResponse;
55
+ const res = {
56
+ json: (data) => { capturedResponse = c.json(data); },
57
+ send: (data) => { capturedResponse = c.html(data); },
58
+ status: (code) => { c.status(code); return res; },
59
+ header: (name, value) => { c.header(name, value); return res; }
60
+ };
61
+ await handler(req, res);
62
+ return capturedResponse;
63
+ };
64
+ }
65
+ get(path, handler) {
66
+ this.app.get(path, this.wrap(handler));
67
+ }
68
+ post(path, handler) {
69
+ this.app.post(path, this.wrap(handler));
70
+ }
71
+ put(path, handler) {
72
+ this.app.put(path, this.wrap(handler));
73
+ }
74
+ delete(path, handler) {
75
+ this.app.delete(path, this.wrap(handler));
76
+ }
77
+ patch(path, handler) {
78
+ this.app.patch(path, this.wrap(handler));
79
+ }
80
+ use(pathOrHandler, handler) {
81
+ if (typeof pathOrHandler === 'string' && handler) {
82
+ // Path based middleware
83
+ // Hono middleware signature is different (c, next) => ...
84
+ this.app.use(pathOrHandler, async (c, next) => {
85
+ // Simplistic conversion
86
+ await handler({}, {}, next);
87
+ });
88
+ }
89
+ else if (typeof pathOrHandler === 'function') {
90
+ // Global middleware
91
+ this.app.use('*', async (c, next) => {
92
+ await pathOrHandler({}, {}, next);
93
+ });
94
+ }
95
+ }
96
+ async listen(port) {
97
+ return new Promise((resolve) => {
98
+ if (this.staticRoot) {
99
+ this.app.get('/*', (0, serve_static_1.serveStatic)({ root: this.staticRoot }));
100
+ }
101
+ this.server = (0, node_server_1.serve)({
102
+ fetch: this.app.fetch,
103
+ port: port || this.port
104
+ }, (info) => {
105
+ resolve();
106
+ });
107
+ });
108
+ }
109
+ // Expose raw app for scenarios where standard interface is not enough
110
+ getRawApp() {
111
+ return this.app;
112
+ }
113
+ async close() {
114
+ if (this.server && typeof this.server.close === 'function') {
115
+ this.server.close();
116
+ }
117
+ }
118
+ }
119
+ exports.HonoHttpServer = HonoHttpServer;
@@ -1,13 +1,30 @@
1
- import { RuntimePlugin, RuntimeContext } from '@objectstack/runtime';
1
+ import { Plugin, PluginContext } from '@objectstack/core';
2
2
  export interface HonoPluginOptions {
3
3
  port?: number;
4
4
  staticRoot?: string;
5
5
  }
6
- export declare class HonoServerPlugin implements RuntimePlugin {
6
+ /**
7
+ * Hono Server Plugin
8
+ *
9
+ * Provides HTTP server capabilities using Hono framework.
10
+ * Registers routes for ObjectStack Runtime Protocol.
11
+ */
12
+ export declare class HonoServerPlugin implements Plugin {
7
13
  name: string;
14
+ version: string;
8
15
  private options;
9
- private app;
16
+ private server;
10
17
  constructor(options?: HonoPluginOptions);
11
- install(ctx: RuntimeContext): void;
12
- onStart(ctx: RuntimeContext): Promise<void>;
18
+ /**
19
+ * Init phase - Setup HTTP server and register as service
20
+ */
21
+ init(ctx: PluginContext): Promise<void>;
22
+ /**
23
+ * Start phase - Bind routes and start listening
24
+ */
25
+ start(ctx: PluginContext): Promise<void>;
26
+ /**
27
+ * Destroy phase - Stop server
28
+ */
29
+ destroy(): Promise<void>;
13
30
  }
@@ -1,117 +1,128 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HonoServerPlugin = void 0;
4
- const node_server_1 = require("@hono/node-server");
5
- const serve_static_1 = require("@hono/node-server/serve-static");
6
- const hono_1 = require("hono");
7
- const cors_1 = require("hono/cors");
8
- const logger_1 = require("hono/logger");
9
- const runtime_1 = require("@objectstack/runtime");
4
+ const adapter_1 = require("./adapter");
5
+ /**
6
+ * Hono Server Plugin
7
+ *
8
+ * Provides HTTP server capabilities using Hono framework.
9
+ * Registers routes for ObjectStack Runtime Protocol.
10
+ */
10
11
  class HonoServerPlugin {
11
- name = 'hono-server';
12
+ name = 'com.objectstack.server.hono';
13
+ version = '1.0.0';
12
14
  options;
13
- app;
15
+ server;
14
16
  constructor(options = {}) {
15
17
  this.options = {
16
18
  port: 3000,
17
19
  ...options
18
20
  };
19
- this.app = new hono_1.Hono();
21
+ this.server = new adapter_1.HonoHttpServer(this.options.port, this.options.staticRoot);
20
22
  }
21
- install(ctx) {
22
- const { engine } = ctx;
23
- const protocol = new runtime_1.ObjectStackRuntimeProtocol(engine);
24
- // Middleware
25
- this.app.use('*', (0, logger_1.logger)());
26
- this.app.use('*', (0, cors_1.cors)());
27
- // --- Bind Protocol to Hono ---
28
- // 1. Discovery
29
- this.app.get('/api/v1', (c) => c.json(protocol.getDiscovery()));
30
- // 2. Meta
31
- this.app.get('/api/v1/meta', (c) => c.json(protocol.getMetaTypes()));
32
- this.app.get('/api/v1/meta/:type', (c) => c.json(protocol.getMetaItems(c.req.param('type'))));
33
- this.app.get('/api/v1/meta/:type/:name', (c) => {
34
- try {
35
- return c.json(protocol.getMetaItem(c.req.param('type'), c.req.param('name')));
36
- }
37
- catch (e) {
38
- return c.json({ error: e.message }, 404);
39
- }
40
- });
41
- // 3. Data
42
- this.app.get('/api/v1/data/:object', async (c) => {
43
- try {
44
- const result = await protocol.findData(c.req.param('object'), c.req.query());
45
- return c.json(result);
46
- }
47
- catch (e) {
48
- return c.json({ error: e.message }, 404);
49
- }
50
- });
51
- this.app.get('/api/v1/data/:object/:id', async (c) => {
52
- try {
53
- const result = await protocol.getData(c.req.param('object'), c.req.param('id'));
54
- return c.json(result);
55
- }
56
- catch (e) {
57
- return c.json({ error: e.message }, 404);
58
- }
59
- });
60
- this.app.post('/api/v1/data/:object', async (c) => {
61
- try {
62
- const body = await c.req.json();
63
- const result = await protocol.createData(c.req.param('object'), body);
64
- return c.json(result, 201);
65
- }
66
- catch (e) {
67
- return c.json({ error: e.message }, 400);
68
- }
69
- });
70
- this.app.patch('/api/v1/data/:object/:id', async (c) => {
71
- try {
72
- const body = await c.req.json();
73
- const result = await protocol.updateData(c.req.param('object'), c.req.param('id'), body);
74
- return c.json(result);
75
- }
76
- catch (e) {
77
- return c.json({ error: e.message }, 400);
78
- }
79
- });
80
- this.app.delete('/api/v1/data/:object/:id', async (c) => {
81
- try {
82
- const result = await protocol.deleteData(c.req.param('object'), c.req.param('id'));
83
- return c.json(result);
84
- }
85
- catch (e) {
86
- return c.json({ error: e.message }, 400);
87
- }
88
- });
89
- // 4. UI Protocol
90
- this.app.get('/api/v1/ui/view/:object', (c) => {
91
- try {
92
- // @ts-ignore
93
- const view = protocol.getUiView(c.req.param('object'), c.req.query('type') || 'list');
94
- return c.json(view);
95
- }
96
- catch (e) {
97
- return c.json({ error: e.message }, 404);
98
- }
99
- });
100
- // Static Files
101
- if (this.options.staticRoot) {
102
- this.app.get('/', (0, serve_static_1.serveStatic)({ root: this.options.staticRoot, path: 'index.html' }));
103
- this.app.get('/*', (0, serve_static_1.serveStatic)({ root: this.options.staticRoot }));
104
- }
105
- console.log(`[HonoPlugin] Installed routes and middleware.`);
23
+ /**
24
+ * Init phase - Setup HTTP server and register as service
25
+ */
26
+ async init(ctx) {
27
+ // Register HTTP server service as IHttpServer
28
+ ctx.registerService('http-server', this.server);
29
+ ctx.logger.log('[HonoServerPlugin] HTTP server service registered');
106
30
  }
107
- async onStart(ctx) {
108
- const port = this.options.port;
109
- console.log(`[HonoPlugin] Starting server...`);
110
- console.log(`✅ Server is running on http://localhost:${port}`);
111
- (0, node_server_1.serve)({
112
- fetch: this.app.fetch,
113
- port
31
+ /**
32
+ * Start phase - Bind routes and start listening
33
+ */
34
+ async start(ctx) {
35
+ // Get protocol implementation instance
36
+ let protocol = null;
37
+ try {
38
+ protocol = ctx.getService('protocol');
39
+ }
40
+ catch (e) {
41
+ ctx.logger.log('[HonoServerPlugin] Protocol service not found, skipping protocol routes');
42
+ }
43
+ // Register protocol routes if available
44
+ if (protocol) {
45
+ const p = protocol;
46
+ this.server.get('/api/v1', (req, res) => res.json(p.getDiscovery()));
47
+ // Meta Protocol
48
+ this.server.get('/api/v1/meta', (req, res) => res.json(p.getMetaTypes()));
49
+ this.server.get('/api/v1/meta/:type', (req, res) => res.json(p.getMetaItems(req.params.type)));
50
+ this.server.get('/api/v1/meta/:type/:name', (req, res) => {
51
+ try {
52
+ res.json(p.getMetaItem(req.params.type, req.params.name));
53
+ }
54
+ catch (e) {
55
+ res.status(404).json({ error: e.message });
56
+ }
57
+ });
58
+ // Data Protocol
59
+ this.server.get('/api/v1/data/:object', async (req, res) => {
60
+ try {
61
+ res.json(await p.findData(req.params.object, req.query));
62
+ }
63
+ catch (e) {
64
+ res.status(400).json({ error: e.message });
65
+ }
66
+ });
67
+ this.server.get('/api/v1/data/:object/:id', async (req, res) => {
68
+ try {
69
+ res.json(await p.getData(req.params.object, req.params.id));
70
+ }
71
+ catch (e) {
72
+ res.status(404).json({ error: e.message });
73
+ }
74
+ });
75
+ this.server.post('/api/v1/data/:object', async (req, res) => {
76
+ try {
77
+ res.status(201).json(await p.createData(req.params.object, req.body));
78
+ }
79
+ catch (e) {
80
+ res.status(400).json({ error: e.message });
81
+ }
82
+ });
83
+ this.server.patch('/api/v1/data/:object/:id', async (req, res) => {
84
+ try {
85
+ res.json(await p.updateData(req.params.object, req.params.id, req.body));
86
+ }
87
+ catch (e) {
88
+ res.status(400).json({ error: e.message });
89
+ }
90
+ });
91
+ this.server.delete('/api/v1/data/:object/:id', async (req, res) => {
92
+ try {
93
+ res.json(await p.deleteData(req.params.object, req.params.id));
94
+ }
95
+ catch (e) {
96
+ res.status(400).json({ error: e.message });
97
+ }
98
+ });
99
+ // UI Protocol
100
+ // @ts-ignore
101
+ this.server.get('/api/v1/ui/view/:object', (req, res) => {
102
+ try {
103
+ const viewType = (req.query.type) || 'list';
104
+ const qt = Array.isArray(viewType) ? viewType[0] : viewType;
105
+ res.json(p.getUiView(req.params.object, qt));
106
+ }
107
+ catch (e) {
108
+ res.status(404).json({ error: e.message });
109
+ }
110
+ });
111
+ }
112
+ // Start server on kernel:ready hook
113
+ ctx.hook('kernel:ready', async () => {
114
+ const port = this.options.port || 3000;
115
+ ctx.logger.log('[HonoServerPlugin] Starting server...');
116
+ await this.server.listen(port);
117
+ ctx.logger.log(`✅ Server is running on http://localhost:${port}`);
114
118
  });
115
119
  }
120
+ /**
121
+ * Destroy phase - Stop server
122
+ */
123
+ async destroy() {
124
+ this.server.close();
125
+ console.log('[HonoServerPlugin] Server stopped');
126
+ }
116
127
  }
117
128
  exports.HonoServerPlugin = HonoServerPlugin;
package/dist/index.d.ts CHANGED
@@ -1,21 +1,2 @@
1
- import { RuntimePlugin, IKernel } from '@objectstack/types';
2
- export interface HonoServerOptions {
3
- port?: number;
4
- staticRoot?: string;
5
- cors?: boolean;
6
- logger?: boolean;
7
- }
8
- /**
9
- * Hono Server Runtime Plugin
10
- *
11
- * Exposes the ObjectStack Kernel via standard HTTP Protocol using Hono.
12
- * Can be used for Production (Standalone) or Development.
13
- */
14
- export declare class HonoServerPlugin implements RuntimePlugin {
15
- name: string;
16
- private options;
17
- constructor(options?: HonoServerOptions);
18
- onStart(ctx: {
19
- engine: IKernel;
20
- }): Promise<void>;
21
- }
1
+ export * from './hono-plugin';
2
+ export * from './adapter';
package/dist/index.js CHANGED
@@ -1,113 +1,18 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HonoServerPlugin = void 0;
4
- const node_server_1 = require("@hono/node-server");
5
- const serve_static_1 = require("@hono/node-server/serve-static");
6
- const hono_1 = require("hono");
7
- const cors_1 = require("hono/cors");
8
- const logger_1 = require("hono/logger");
9
- const runtime_1 = require("@objectstack/runtime");
10
- /**
11
- * Hono Server Runtime Plugin
12
- *
13
- * Exposes the ObjectStack Kernel via standard HTTP Protocol using Hono.
14
- * Can be used for Production (Standalone) or Development.
15
- */
16
- class HonoServerPlugin {
17
- name = 'com.objectstack.server.hono';
18
- options;
19
- constructor(options = {}) {
20
- this.options = {
21
- port: 3000,
22
- cors: true,
23
- logger: true,
24
- ...options
25
- };
26
- }
27
- async onStart(ctx) {
28
- const app = new hono_1.Hono();
29
- // TODO: Protocol needs access to actual Kernel instance or we make Protocol an interface too
30
- const protocol = new runtime_1.ObjectStackRuntimeProtocol(ctx.engine);
31
- // 1. Middlewares
32
- if (this.options.logger)
33
- app.use('*', (0, logger_1.logger)());
34
- if (this.options.cors)
35
- app.use('*', (0, cors_1.cors)());
36
- // 2. Wiring Protocol (Automatic)
37
- // Discovery
38
- app.get('/api/v1', (c) => c.json(protocol.getDiscovery()));
39
- // Meta Protocol
40
- app.get('/api/v1/meta', (c) => c.json(protocol.getMetaTypes()));
41
- app.get('/api/v1/meta/:type', (c) => c.json(protocol.getMetaItems(c.req.param('type'))));
42
- app.get('/api/v1/meta/:type/:name', (c) => {
43
- try {
44
- return c.json(protocol.getMetaItem(c.req.param('type'), c.req.param('name')));
45
- }
46
- catch (e) {
47
- return c.json({ error: e.message }, 404);
48
- }
49
- });
50
- // Data Protocol
51
- app.get('/api/v1/data/:object', async (c) => {
52
- try {
53
- return c.json(await protocol.findData(c.req.param('object'), c.req.query()));
54
- }
55
- catch (e) {
56
- return c.json({ error: e.message }, 400);
57
- }
58
- });
59
- app.get('/api/v1/data/:object/:id', async (c) => {
60
- try {
61
- return c.json(await protocol.getData(c.req.param('object'), c.req.param('id')));
62
- }
63
- catch (e) {
64
- return c.json({ error: e.message }, 404);
65
- }
66
- });
67
- app.post('/api/v1/data/:object', async (c) => {
68
- try {
69
- return c.json(await protocol.createData(c.req.param('object'), await c.req.json()), 201);
70
- }
71
- catch (e) {
72
- return c.json({ error: e.message }, 400);
73
- }
74
- });
75
- app.patch('/api/v1/data/:object/:id', async (c) => {
76
- try {
77
- return c.json(await protocol.updateData(c.req.param('object'), c.req.param('id'), await c.req.json()));
78
- }
79
- catch (e) {
80
- return c.json({ error: e.message }, 400);
81
- }
82
- });
83
- app.delete('/api/v1/data/:object/:id', async (c) => {
84
- try {
85
- return c.json(await protocol.deleteData(c.req.param('object'), c.req.param('id')));
86
- }
87
- catch (e) {
88
- return c.json({ error: e.message }, 400);
89
- }
90
- });
91
- // UI Protocol
92
- // @ts-ignore
93
- app.get('/api/v1/ui/view/:object', (c) => {
94
- try {
95
- const viewType = c.req.query('type') || 'list';
96
- return c.json(protocol.getUiView(c.req.param('object'), viewType));
97
- }
98
- catch (e) {
99
- return c.json({ error: e.message }, 404);
100
- }
101
- });
102
- // 3. Static Files (Optional)
103
- if (this.options.staticRoot) {
104
- app.get('/', (0, serve_static_1.serveStatic)({ root: this.options.staticRoot, path: 'index.html' }));
105
- app.get('/*', (0, serve_static_1.serveStatic)({ root: this.options.staticRoot }));
106
- }
107
- console.log('');
108
- console.log(`🌍 ObjectStack Server (Hono) running at: http://localhost:${this.options.port}`);
109
- console.log('');
110
- (0, node_server_1.serve)({ fetch: app.fetch, port: this.options.port });
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
111
7
  }
112
- }
113
- exports.HonoServerPlugin = HonoServerPlugin;
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./hono-plugin"), exports);
18
+ __exportStar(require("./adapter"), exports);
package/package.json CHANGED
@@ -1,22 +1,19 @@
1
1
  {
2
2
  "name": "@objectstack/plugin-hono-server",
3
- "version": "0.4.2",
3
+ "version": "0.6.1",
4
4
  "description": "Standard Hono Server Adapter for ObjectStack Runtime",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "dependencies": {
8
8
  "@hono/node-server": "^1.2.0",
9
9
  "hono": "^4.0.0",
10
- "@objectstack/spec": "0.4.2",
11
- "@objectstack/types": "0.4.2"
10
+ "@objectstack/core": "0.6.1",
11
+ "@objectstack/spec": "0.6.1",
12
+ "@objectstack/types": "0.6.1"
12
13
  },
13
14
  "devDependencies": {
14
15
  "@types/node": "^20.0.0",
15
- "typescript": "^5.0.0",
16
- "@objectstack/runtime": "0.4.2"
17
- },
18
- "peerDependencies": {
19
- "@objectstack/runtime": "^0.4.2"
16
+ "typescript": "^5.0.0"
20
17
  },
21
18
  "scripts": {
22
19
  "build": "tsc"
package/src/adapter.ts ADDED
@@ -0,0 +1,117 @@
1
+ // Export IHttpServer from core
2
+ export * from '@objectstack/core';
3
+
4
+ import {
5
+ IHttpServer,
6
+ RouteHandler,
7
+ Middleware
8
+ } from '@objectstack/core';
9
+ import { Hono } from 'hono';
10
+ import { serve } from '@hono/node-server';
11
+ import { serveStatic } from '@hono/node-server/serve-static';
12
+
13
+ /**
14
+ * Hono Implementation of IHttpServer
15
+ */
16
+ export class HonoHttpServer implements IHttpServer {
17
+ private app: Hono;
18
+ private server: any;
19
+
20
+ constructor(
21
+ private port: number = 3000,
22
+ private staticRoot?: string
23
+ ) {
24
+ this.app = new Hono();
25
+ }
26
+
27
+ // internal helper to convert standard handler to Hono handler
28
+ private wrap(handler: RouteHandler) {
29
+ return async (c: any) => {
30
+ const req = {
31
+ params: c.req.param(),
32
+ query: c.req.query(),
33
+ body: await c.req.parseBody().catch(() => {}), // fallback
34
+ headers: c.req.header(),
35
+ method: c.req.method,
36
+ path: c.req.path
37
+ };
38
+
39
+ // Try to parse JSON body if possible
40
+ if (c.req.header('content-type')?.includes('application/json')) {
41
+ try { req.body = await c.req.json(); } catch(e) {}
42
+ }
43
+
44
+ let capturedResponse: any;
45
+
46
+ const res = {
47
+ json: (data: any) => { capturedResponse = c.json(data); },
48
+ send: (data: string) => { capturedResponse = c.html(data); },
49
+ status: (code: number) => { c.status(code); return res; },
50
+ header: (name: string, value: string) => { c.header(name, value); return res; }
51
+ };
52
+
53
+ await handler(req as any, res as any);
54
+ return capturedResponse;
55
+ };
56
+ }
57
+
58
+ get(path: string, handler: RouteHandler) {
59
+ this.app.get(path, this.wrap(handler));
60
+ }
61
+ post(path: string, handler: RouteHandler) {
62
+ this.app.post(path, this.wrap(handler));
63
+ }
64
+ put(path: string, handler: RouteHandler) {
65
+ this.app.put(path, this.wrap(handler));
66
+ }
67
+ delete(path: string, handler: RouteHandler) {
68
+ this.app.delete(path, this.wrap(handler));
69
+ }
70
+ patch(path: string, handler: RouteHandler) {
71
+ this.app.patch(path, this.wrap(handler));
72
+ }
73
+
74
+ use(pathOrHandler: string | Middleware, handler?: Middleware) {
75
+ if (typeof pathOrHandler === 'string' && handler) {
76
+ // Path based middleware
77
+ // Hono middleware signature is different (c, next) => ...
78
+ this.app.use(pathOrHandler, async (c, next) => {
79
+ // Simplistic conversion
80
+ await handler({} as any, {} as any, next);
81
+ });
82
+ } else if (typeof pathOrHandler === 'function') {
83
+ // Global middleware
84
+ this.app.use('*', async (c, next) => {
85
+ await pathOrHandler({} as any, {} as any, next);
86
+ });
87
+ }
88
+ }
89
+
90
+ async listen(port: number) {
91
+ return new Promise<void>((resolve) => {
92
+ if (this.staticRoot) {
93
+ this.app.get('/*', serveStatic({ root: this.staticRoot }));
94
+ }
95
+
96
+ this.server = serve({
97
+ fetch: this.app.fetch,
98
+ port: port || this.port
99
+ }, (info) => {
100
+ resolve();
101
+ });
102
+ });
103
+ }
104
+
105
+ // Expose raw app for scenarios where standard interface is not enough
106
+ getRawApp() {
107
+ return this.app;
108
+ }
109
+
110
+ async close() {
111
+ if (this.server && typeof this.server.close === 'function') {
112
+ this.server.close();
113
+ }
114
+ }
115
+
116
+
117
+ }
@@ -1,128 +1,120 @@
1
- import { serve } from '@hono/node-server';
2
- import { serveStatic } from '@hono/node-server/serve-static';
3
- import { Hono } from 'hono';
4
- import { cors } from 'hono/cors';
5
- import { logger } from 'hono/logger';
6
- import { RuntimePlugin, RuntimeContext, ObjectStackRuntimeProtocol } from '@objectstack/runtime';
1
+ import { Plugin, PluginContext, IHttpServer } from '@objectstack/core';
2
+ import { IObjectStackProtocol } from '@objectstack/spec/api';
3
+ import { HonoHttpServer } from './adapter';
7
4
 
8
5
  export interface HonoPluginOptions {
9
6
  port?: number;
10
7
  staticRoot?: string;
11
8
  }
12
9
 
13
- export class HonoServerPlugin implements RuntimePlugin {
14
- name = 'hono-server';
10
+ /**
11
+ * Hono Server Plugin
12
+ *
13
+ * Provides HTTP server capabilities using Hono framework.
14
+ * Registers routes for ObjectStack Runtime Protocol.
15
+ */
16
+ export class HonoServerPlugin implements Plugin {
17
+ name = 'com.objectstack.server.hono';
18
+ version = '1.0.0';
19
+
15
20
  private options: HonoPluginOptions;
16
- private app: Hono;
21
+ private server: HonoHttpServer;
17
22
 
18
23
  constructor(options: HonoPluginOptions = {}) {
19
24
  this.options = {
20
25
  port: 3000,
21
26
  ...options
22
27
  };
23
- this.app = new Hono();
28
+ this.server = new HonoHttpServer(this.options.port, this.options.staticRoot);
24
29
  }
25
30
 
26
- install(ctx: RuntimeContext) {
27
- const { engine } = ctx;
28
- const protocol = new ObjectStackRuntimeProtocol(engine);
29
-
30
- // Middleware
31
- this.app.use('*', logger());
32
- this.app.use('*', cors());
31
+ /**
32
+ * Init phase - Setup HTTP server and register as service
33
+ */
34
+ async init(ctx: PluginContext) {
35
+ // Register HTTP server service as IHttpServer
36
+ ctx.registerService('http-server', this.server);
37
+ ctx.logger.log('[HonoServerPlugin] HTTP server service registered');
38
+ }
33
39
 
34
- // --- Bind Protocol to Hono ---
40
+ /**
41
+ * Start phase - Bind routes and start listening
42
+ */
43
+ async start(ctx: PluginContext) {
44
+ // Get protocol implementation instance
45
+ let protocol: IObjectStackProtocol | null = null;
35
46
 
36
- // 1. Discovery
37
- this.app.get('/api/v1', (c) => c.json(protocol.getDiscovery()));
38
-
39
- // 2. Meta
40
- this.app.get('/api/v1/meta', (c) => c.json(protocol.getMetaTypes()));
41
- this.app.get('/api/v1/meta/:type', (c) => c.json(protocol.getMetaItems(c.req.param('type'))));
42
- this.app.get('/api/v1/meta/:type/:name', (c) => {
43
- try {
44
- return c.json(protocol.getMetaItem(c.req.param('type'), c.req.param('name')));
45
- } catch (e: any) {
46
- return c.json({ error: e.message }, 404);
47
- }
48
- });
49
-
50
- // 3. Data
51
- this.app.get('/api/v1/data/:object', async (c) => {
52
- try {
53
- const result = await protocol.findData(c.req.param('object'), c.req.query());
54
- return c.json(result);
55
- } catch (e: any) {
56
- return c.json({ error: e.message }, 404);
57
- }
58
- });
59
-
60
- this.app.get('/api/v1/data/:object/:id', async (c) => {
61
- try {
62
- const result = await protocol.getData(c.req.param('object'), c.req.param('id'));
63
- return c.json(result);
64
- } catch (e: any) {
65
- return c.json({ error: e.message }, 404);
66
- }
67
- });
68
-
69
- this.app.post('/api/v1/data/:object', async (c) => {
70
- try {
71
- const body = await c.req.json();
72
- const result = await protocol.createData(c.req.param('object'), body);
73
- return c.json(result, 201);
74
- } catch (e: any) {
75
- return c.json({ error: e.message }, 400);
76
- }
77
- });
47
+ try {
48
+ protocol = ctx.getService<IObjectStackProtocol>('protocol');
49
+ } catch (e) {
50
+ ctx.logger.log('[HonoServerPlugin] Protocol service not found, skipping protocol routes');
51
+ }
78
52
 
79
- this.app.patch('/api/v1/data/:object/:id', async (c) => {
80
- try {
81
- const body = await c.req.json();
82
- const result = await protocol.updateData(c.req.param('object'), c.req.param('id'), body);
83
- return c.json(result);
84
- } catch (e: any) {
85
- return c.json({ error: e.message }, 400);
86
- }
87
- });
53
+ // Register protocol routes if available
54
+ if (protocol) {
55
+ const p = protocol!;
56
+ this.server.get('/api/v1', (req, res) => res.json(p.getDiscovery()));
88
57
 
89
- this.app.delete('/api/v1/data/:object/:id', async (c) => {
90
- try {
91
- const result = await protocol.deleteData(c.req.param('object'), c.req.param('id'));
92
- return c.json(result);
93
- } catch (e: any) {
94
- return c.json({ error: e.message }, 400);
95
- }
96
- });
97
-
98
- // 4. UI Protocol
99
- this.app.get('/api/v1/ui/view/:object', (c) => {
100
- try {
101
- // @ts-ignore
102
- const view = protocol.getUiView(c.req.param('object'), c.req.query('type') || 'list');
103
- return c.json(view);
104
- } catch (e: any) {
105
- return c.json({ error: e.message }, 404);
106
- }
107
- });
58
+ // Meta Protocol
59
+ this.server.get('/api/v1/meta', (req, res) => res.json(p.getMetaTypes()));
60
+ this.server.get('/api/v1/meta/:type', (req, res) => res.json(p.getMetaItems(req.params.type)));
61
+ this.server.get('/api/v1/meta/:type/:name', (req, res) => {
62
+ try {
63
+ res.json(p.getMetaItem(req.params.type, req.params.name));
64
+ } catch(e:any) {
65
+ res.status(404).json({error: e.message});
66
+ }
67
+ });
68
+
69
+ // Data Protocol
70
+ this.server.get('/api/v1/data/:object', async (req, res) => {
71
+ try { res.json(await p.findData(req.params.object, req.query)); }
72
+ catch(e:any) { res.status(400).json({error:e.message}); }
73
+ });
74
+ this.server.get('/api/v1/data/:object/:id', async (req, res) => {
75
+ try { res.json(await p.getData(req.params.object, req.params.id)); }
76
+ catch(e:any) { res.status(404).json({error:e.message}); }
77
+ });
78
+ this.server.post('/api/v1/data/:object', async (req, res) => {
79
+ try { res.status(201).json(await p.createData(req.params.object, req.body)); }
80
+ catch(e:any) { res.status(400).json({error:e.message}); }
81
+ });
82
+ this.server.patch('/api/v1/data/:object/:id', async (req, res) => {
83
+ try { res.json(await p.updateData(req.params.object, req.params.id, req.body)); }
84
+ catch(e:any) { res.status(400).json({error:e.message}); }
85
+ });
86
+ this.server.delete('/api/v1/data/:object/:id', async (req, res) => {
87
+ try { res.json(await p.deleteData(req.params.object, req.params.id)); }
88
+ catch(e:any) { res.status(400).json({error:e.message}); }
89
+ });
108
90
 
109
- // Static Files
110
- if (this.options.staticRoot) {
111
- this.app.get('/', serveStatic({ root: this.options.staticRoot, path: 'index.html' }));
112
- this.app.get('/*', serveStatic({ root: this.options.staticRoot }));
91
+ // UI Protocol
92
+ // @ts-ignore
93
+ this.server.get('/api/v1/ui/view/:object', (req, res) => {
94
+ try {
95
+ const viewType = (req.query.type) || 'list';
96
+ const qt = Array.isArray(viewType) ? viewType[0] : viewType;
97
+ res.json(p.getUiView(req.params.object, qt as any));
98
+ }
99
+ catch(e:any) { res.status(404).json({error:e.message}); }
100
+ });
113
101
  }
114
102
 
115
- console.log(`[HonoPlugin] Installed routes and middleware.`);
103
+ // Start server on kernel:ready hook
104
+ ctx.hook('kernel:ready', async () => {
105
+ const port = this.options.port || 3000;
106
+ ctx.logger.log('[HonoServerPlugin] Starting server...');
107
+
108
+ await this.server.listen(port);
109
+ ctx.logger.log(`✅ Server is running on http://localhost:${port}`);
110
+ });
116
111
  }
117
112
 
118
- async onStart(ctx: RuntimeContext) {
119
- const port = this.options.port;
120
- console.log(`[HonoPlugin] Starting server...`);
121
- console.log(`✅ Server is running on http://localhost:${port}`);
122
-
123
- serve({
124
- fetch: this.app.fetch,
125
- port
126
- });
113
+ /**
114
+ * Destroy phase - Stop server
115
+ */
116
+ async destroy() {
117
+ this.server.close();
118
+ console.log('[HonoServerPlugin] Server stopped');
127
119
  }
128
120
  }
package/src/index.ts CHANGED
@@ -1,104 +1,3 @@
1
- import { serve } from '@hono/node-server';
2
- import { serveStatic } from '@hono/node-server/serve-static';
3
- import { Hono } from 'hono';
4
- import { cors } from 'hono/cors';
5
- import { logger } from 'hono/logger';
6
- import { ObjectStackRuntimeProtocol } from '@objectstack/runtime';
7
- import { RuntimePlugin, IKernel } from '@objectstack/types';
1
+ export * from './hono-plugin';
2
+ export * from './adapter';
8
3
 
9
- export interface HonoServerOptions {
10
- port?: number;
11
- staticRoot?: string;
12
- cors?: boolean;
13
- logger?: boolean;
14
- }
15
-
16
- /**
17
- * Hono Server Runtime Plugin
18
- *
19
- * Exposes the ObjectStack Kernel via standard HTTP Protocol using Hono.
20
- * Can be used for Production (Standalone) or Development.
21
- */
22
- export class HonoServerPlugin implements RuntimePlugin {
23
- name = 'com.objectstack.server.hono';
24
-
25
- private options: HonoServerOptions;
26
-
27
- constructor(options: HonoServerOptions = {}) {
28
- this.options = {
29
- port: 3000,
30
- cors: true,
31
- logger: true,
32
- ...options
33
- };
34
- }
35
-
36
- async onStart(ctx: { engine: IKernel }) {
37
- const app = new Hono();
38
- // TODO: Protocol needs access to actual Kernel instance or we make Protocol an interface too
39
- const protocol = new ObjectStackRuntimeProtocol(ctx.engine as any);
40
-
41
- // 1. Middlewares
42
- if (this.options.logger) app.use('*', logger());
43
- if (this.options.cors) app.use('*', cors());
44
-
45
- // 2. Wiring Protocol (Automatic)
46
- // Discovery
47
- app.get('/api/v1', (c) => c.json(protocol.getDiscovery()));
48
-
49
- // Meta Protocol
50
- app.get('/api/v1/meta', (c) => c.json(protocol.getMetaTypes()));
51
- app.get('/api/v1/meta/:type', (c) => c.json(protocol.getMetaItems(c.req.param('type'))));
52
- app.get('/api/v1/meta/:type/:name', (c) => {
53
- try {
54
- return c.json(protocol.getMetaItem(c.req.param('type'), c.req.param('name')));
55
- } catch(e:any) {
56
- return c.json({error: e.message}, 404);
57
- }
58
- });
59
-
60
- // Data Protocol
61
- app.get('/api/v1/data/:object', async (c) => {
62
- try { return c.json(await protocol.findData(c.req.param('object'), c.req.query())); }
63
- catch(e:any) { return c.json({error:e.message}, 400); }
64
- });
65
- app.get('/api/v1/data/:object/:id', async (c) => {
66
- try { return c.json(await protocol.getData(c.req.param('object'), c.req.param('id'))); }
67
- catch(e:any) { return c.json({error:e.message}, 404); }
68
- });
69
- app.post('/api/v1/data/:object', async (c) => {
70
- try { return c.json(await protocol.createData(c.req.param('object'), await c.req.json()), 201); }
71
- catch(e:any) { return c.json({error:e.message}, 400); }
72
- });
73
- app.patch('/api/v1/data/:object/:id', async (c) => {
74
- try { return c.json(await protocol.updateData(c.req.param('object'), c.req.param('id'), await c.req.json())); }
75
- catch(e:any) { return c.json({error:e.message}, 400); }
76
- });
77
- app.delete('/api/v1/data/:object/:id', async (c) => {
78
- try { return c.json(await protocol.deleteData(c.req.param('object'), c.req.param('id'))); }
79
- catch(e:any) { return c.json({error:e.message}, 400); }
80
- });
81
-
82
- // UI Protocol
83
- // @ts-ignore
84
- app.get('/api/v1/ui/view/:object', (c) => {
85
- try {
86
- const viewType = (c.req.query('type') as 'list' | 'form') || 'list';
87
- return c.json(protocol.getUiView(c.req.param('object'), viewType));
88
- }
89
- catch(e:any) { return c.json({error:e.message}, 404); }
90
- });
91
-
92
- // 3. Static Files (Optional)
93
- if (this.options.staticRoot) {
94
- app.get('/', serveStatic({ root: this.options.staticRoot, path: 'index.html' }));
95
- app.get('/*', serveStatic({ root: this.options.staticRoot }));
96
- }
97
-
98
- console.log('');
99
- console.log(`🌍 ObjectStack Server (Hono) running at: http://localhost:${this.options.port}`);
100
- console.log('');
101
-
102
- serve({ fetch: app.fetch, port: this.options.port });
103
- }
104
- }