@objectstack/plugin-hono-server 0.4.1 → 0.6.0
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 +27 -0
- package/dist/adapter.d.ts +23 -0
- package/dist/adapter.js +119 -0
- package/dist/hono-plugin.d.ts +22 -5
- package/dist/hono-plugin.js +112 -101
- package/dist/index.d.ts +2 -21
- package/dist/index.js +16 -111
- package/package.json +5 -8
- package/src/adapter.ts +117 -0
- package/src/hono-plugin.ts +94 -102
- package/src/index.ts +2 -103
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @objectstack/plugin-hono-server
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b2df5f7: Unified version bump to 0.5.0
|
|
8
|
+
|
|
9
|
+
- Standardized all package versions to 0.5.0 across the monorepo
|
|
10
|
+
- Fixed driver-memory package.json paths for proper module resolution
|
|
11
|
+
- Ensured all packages are in sync for the 0.5.0 release
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [b2df5f7]
|
|
16
|
+
- @objectstack/spec@0.6.0
|
|
17
|
+
- @objectstack/types@0.6.0
|
|
18
|
+
- @objectstack/core@0.6.0
|
|
19
|
+
|
|
20
|
+
## 0.4.2
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Unify all package versions to 0.4.2
|
|
25
|
+
- Updated dependencies
|
|
26
|
+
- @objectstack/spec@0.4.2
|
|
27
|
+
- @objectstack/runtime@0.4.2
|
|
28
|
+
- @objectstack/types@0.4.2
|
|
29
|
+
|
|
3
30
|
## 0.4.1
|
|
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
|
+
}
|
package/dist/adapter.js
ADDED
|
@@ -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;
|
package/dist/hono-plugin.d.ts
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
2
2
|
export interface HonoPluginOptions {
|
|
3
3
|
port?: number;
|
|
4
4
|
staticRoot?: string;
|
|
5
5
|
}
|
|
6
|
-
|
|
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
|
|
16
|
+
private server;
|
|
10
17
|
constructor(options?: HonoPluginOptions);
|
|
11
|
-
|
|
12
|
-
|
|
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
|
}
|
package/dist/hono-plugin.js
CHANGED
|
@@ -1,117 +1,128 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HonoServerPlugin = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
12
|
+
name = 'com.objectstack.server.hono';
|
|
13
|
+
version = '1.0.0';
|
|
12
14
|
options;
|
|
13
|
-
|
|
15
|
+
server;
|
|
14
16
|
constructor(options = {}) {
|
|
15
17
|
this.options = {
|
|
16
18
|
port: 3000,
|
|
17
19
|
...options
|
|
18
20
|
};
|
|
19
|
-
this.
|
|
21
|
+
this.server = new adapter_1.HonoHttpServer(this.options.port, this.options.staticRoot);
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
2
|
-
export
|
|
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.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.6.0",
|
|
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/
|
|
11
|
-
"@objectstack/
|
|
10
|
+
"@objectstack/core": "0.6.0",
|
|
11
|
+
"@objectstack/spec": "0.6.0",
|
|
12
|
+
"@objectstack/types": "0.6.0"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|
|
14
15
|
"@types/node": "^20.0.0",
|
|
15
|
-
"typescript": "^5.0.0"
|
|
16
|
-
"@objectstack/runtime": "0.4.1"
|
|
17
|
-
},
|
|
18
|
-
"peerDependencies": {
|
|
19
|
-
"@objectstack/runtime": "^0.4.1"
|
|
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
|
+
}
|
package/src/hono-plugin.ts
CHANGED
|
@@ -1,128 +1,120 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
14
|
-
|
|
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
|
|
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.
|
|
28
|
+
this.server = new HonoHttpServer(this.options.port, this.options.staticRoot);
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
this.
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
}
|