@objectstack/hono 2.0.7 → 3.0.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/hono@2.0.7 build /home/runner/work/spec/spec/packages/adapters/hono
2
+ > @objectstack/hono@3.0.0 build /home/runner/work/spec/spec/packages/adapters/hono
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 6.11 KB
14
- ESM dist/index.mjs.map 13.45 KB
13
+ ESM dist/index.mjs 216.00 B
14
+ ESM dist/index.mjs.map 648.00 B
15
15
  ESM ⚡️ Build success in 43ms
16
- CJS dist/index.js 7.20 KB
17
- CJS dist/index.js.map 13.49 KB
16
+ CJS dist/index.js 1.22 KB
17
+ CJS dist/index.js.map 692.00 B
18
18
  CJS ⚡️ Build success in 43ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 7606ms
21
- DTS dist/index.d.mts 1.22 KB
22
- DTS dist/index.d.ts 1.22 KB
20
+ DTS ⚡️ Build success in 7485ms
21
+ DTS dist/index.d.mts 352.00 B
22
+ DTS dist/index.d.ts 352.00 B
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @objectstack/hono
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - Release v3.0.0 — unified version bump for all ObjectStack packages.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @objectstack/runtime@3.0.0
13
+
3
14
  ## 2.0.7
4
15
 
5
16
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,33 +1,12 @@
1
- import * as hono_types from 'hono/types';
2
- import { Hono } from 'hono';
3
1
  import { ObjectKernel } from '@objectstack/runtime';
4
2
 
5
3
  interface ObjectStackHonoOptions {
6
4
  kernel: ObjectKernel;
7
5
  prefix?: string;
8
6
  }
9
- /**
10
- * @deprecated Use `HonoServerPlugin` + `createRestApiPlugin()` + `createDispatcherPlugin()` instead.
11
- * This function bundles all routes into a single Hono app using the legacy HttpDispatcher.
12
- * The plugin-based approach provides better modularity and separation of concerns.
13
- *
14
- * Migration:
15
- * ```ts
16
- * // Before:
17
- * const app = createHonoApp({ kernel, prefix: '/api/v1' });
18
- *
19
- * // After:
20
- * import { createRestApiPlugin } from '@objectstack/rest';
21
- * import { createDispatcherPlugin } from '@objectstack/runtime';
22
- * kernel.use(new HonoServerPlugin({ port: 3000 }));
23
- * kernel.use(createRestApiPlugin());
24
- * kernel.use(createDispatcherPlugin({ prefix: '/api/v1' }));
25
- * ```
26
- */
27
- declare function createHonoApp(options: ObjectStackHonoOptions): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
28
7
  /**
29
8
  * Middleware mode for existing Hono apps
30
9
  */
31
10
  declare function objectStackMiddleware(kernel: ObjectKernel): (c: any, next: any) => Promise<void>;
32
11
 
33
- export { type ObjectStackHonoOptions, createHonoApp, objectStackMiddleware };
12
+ export { type ObjectStackHonoOptions, objectStackMiddleware };
package/dist/index.d.ts CHANGED
@@ -1,33 +1,12 @@
1
- import * as hono_types from 'hono/types';
2
- import { Hono } from 'hono';
3
1
  import { ObjectKernel } from '@objectstack/runtime';
4
2
 
5
3
  interface ObjectStackHonoOptions {
6
4
  kernel: ObjectKernel;
7
5
  prefix?: string;
8
6
  }
9
- /**
10
- * @deprecated Use `HonoServerPlugin` + `createRestApiPlugin()` + `createDispatcherPlugin()` instead.
11
- * This function bundles all routes into a single Hono app using the legacy HttpDispatcher.
12
- * The plugin-based approach provides better modularity and separation of concerns.
13
- *
14
- * Migration:
15
- * ```ts
16
- * // Before:
17
- * const app = createHonoApp({ kernel, prefix: '/api/v1' });
18
- *
19
- * // After:
20
- * import { createRestApiPlugin } from '@objectstack/rest';
21
- * import { createDispatcherPlugin } from '@objectstack/runtime';
22
- * kernel.use(new HonoServerPlugin({ port: 3000 }));
23
- * kernel.use(createRestApiPlugin());
24
- * kernel.use(createDispatcherPlugin({ prefix: '/api/v1' }));
25
- * ```
26
- */
27
- declare function createHonoApp(options: ObjectStackHonoOptions): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
28
7
  /**
29
8
  * Middleware mode for existing Hono apps
30
9
  */
31
10
  declare function objectStackMiddleware(kernel: ObjectKernel): (c: any, next: any) => Promise<void>;
32
11
 
33
- export { type ObjectStackHonoOptions, createHonoApp, objectStackMiddleware };
12
+ export { type ObjectStackHonoOptions, objectStackMiddleware };
package/dist/index.js CHANGED
@@ -20,158 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- createHonoApp: () => createHonoApp,
24
23
  objectStackMiddleware: () => objectStackMiddleware
25
24
  });
26
25
  module.exports = __toCommonJS(index_exports);
27
- var import_hono = require("hono");
28
- var import_cors = require("hono/cors");
29
- var import_runtime = require("@objectstack/runtime");
30
- function createHonoApp(options) {
31
- const app = new import_hono.Hono();
32
- const { prefix = "/api" } = options;
33
- const dispatcher = new import_runtime.HttpDispatcher(options.kernel);
34
- app.use("*", (0, import_cors.cors)());
35
- const normalizeResponse = (c, result) => {
36
- if (result.handled) {
37
- if (result.response) {
38
- return c.json(result.response.body, result.response.status, result.response.headers);
39
- }
40
- if (result.result) {
41
- const res = result.result;
42
- if (res.type === "redirect" && res.url) {
43
- return c.redirect(res.url);
44
- }
45
- if (res.type === "stream" && res.stream) {
46
- return c.body(res.stream, 200, res.headers);
47
- }
48
- return res;
49
- }
50
- }
51
- return c.json({ success: false, error: { message: "Not Found", code: 404 } }, 404);
52
- };
53
- app.get(prefix, (c) => {
54
- return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });
55
- });
56
- app.all(`${prefix}/auth/*`, async (c) => {
57
- try {
58
- const authService = typeof options.kernel.getService === "function" ? options.kernel.getService("auth") : null;
59
- if (authService && typeof authService.handleRequest === "function") {
60
- const response = await authService.handleRequest(c.req.raw);
61
- return response;
62
- }
63
- const path = c.req.path.substring(c.req.path.indexOf("/auth/") + 6);
64
- const body = await c.req.parseBody().catch(() => ({}));
65
- const result = await dispatcher.handleAuth(path, c.req.method, body, { request: c.req.raw });
66
- return normalizeResponse(c, result);
67
- } catch (err) {
68
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
69
- }
70
- });
71
- app.post(`${prefix}/graphql`, async (c) => {
72
- try {
73
- const body = await c.req.json();
74
- const result = await dispatcher.handleGraphQL(body, { request: c.req.raw });
75
- return c.json(result);
76
- } catch (err) {
77
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
78
- }
79
- });
80
- app.all(`${prefix}/meta*`, async (c) => {
81
- try {
82
- const path = c.req.path.substring(c.req.path.indexOf("/meta") + 5);
83
- const method = c.req.method;
84
- let body = void 0;
85
- if (method === "PUT" || method === "POST") {
86
- try {
87
- body = await c.req.json();
88
- } catch (e) {
89
- body = {};
90
- }
91
- }
92
- const query = c.req.query();
93
- const result = await dispatcher.handleMetadata(path, { request: c.req.raw }, method, body, query);
94
- return normalizeResponse(c, result);
95
- } catch (err) {
96
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
97
- }
98
- });
99
- app.all(`${prefix}/data*`, async (c) => {
100
- try {
101
- const path = c.req.path.substring(c.req.path.indexOf("/data") + 5);
102
- const method = c.req.method;
103
- let body = {};
104
- if (method === "POST" || method === "PATCH") {
105
- body = await c.req.json().catch(() => ({}));
106
- }
107
- const query = c.req.query();
108
- const result = await dispatcher.handleData(path, method, body, query, { request: c.req.raw });
109
- return normalizeResponse(c, result);
110
- } catch (err) {
111
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
112
- }
113
- });
114
- app.all(`${prefix}/analytics*`, async (c) => {
115
- try {
116
- const path = c.req.path.substring(c.req.path.indexOf("/analytics") + 10);
117
- const method = c.req.method;
118
- let body = {};
119
- if (method === "POST") {
120
- body = await c.req.json().catch(() => ({}));
121
- }
122
- const result = await dispatcher.handleAnalytics(path, method, body, { request: c.req.raw });
123
- return normalizeResponse(c, result);
124
- } catch (err) {
125
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
126
- }
127
- });
128
- app.all(`${prefix}/automation*`, async (c) => {
129
- try {
130
- const path = c.req.path.substring(c.req.path.indexOf("/automation") + 11);
131
- const method = c.req.method;
132
- let body = {};
133
- if (method === "POST") {
134
- body = await c.req.json().catch(() => ({}));
135
- }
136
- const result = await dispatcher.handleAutomation(path, method, body, { request: c.req.raw });
137
- return normalizeResponse(c, result);
138
- } catch (err) {
139
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
140
- }
141
- });
142
- app.all(`${prefix}/storage*`, async (c) => {
143
- try {
144
- const path = c.req.path.substring(c.req.path.indexOf("/storage") + 8);
145
- const method = c.req.method;
146
- let file = void 0;
147
- if (method === "POST" && path.includes("upload")) {
148
- const body = await c.req.parseBody();
149
- file = body["file"];
150
- }
151
- const result = await dispatcher.handleStorage(path, method, file, { request: c.req.raw });
152
- return normalizeResponse(c, result);
153
- } catch (err) {
154
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
155
- }
156
- });
157
- app.all(`${prefix}/packages*`, async (c) => {
158
- try {
159
- const packagesIndex = c.req.path.indexOf("/packages");
160
- const path = c.req.path.substring(packagesIndex + 9);
161
- const method = c.req.method;
162
- let body = {};
163
- if (method === "POST" || method === "PATCH" || method === "PUT") {
164
- body = await c.req.json().catch(() => ({}));
165
- }
166
- const query = c.req.query();
167
- const result = await dispatcher.handlePackages(path, method, body, query, { request: c.req.raw });
168
- return normalizeResponse(c, result);
169
- } catch (err) {
170
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
171
- }
172
- });
173
- return app;
174
- }
175
26
  function objectStackMiddleware(kernel) {
176
27
  return async (c, next) => {
177
28
  c.set("objectStack", kernel);
@@ -180,7 +31,6 @@ function objectStackMiddleware(kernel) {
180
31
  }
181
32
  // Annotate the CommonJS export names for ESM import in node:
182
33
  0 && (module.exports = {
183
- createHonoApp,
184
34
  objectStackMiddleware
185
35
  });
186
36
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';\n\nexport interface ObjectStackHonoOptions {\n kernel: ObjectKernel;\n prefix?: string;\n}\n\n/**\n * Auth service interface with handleRequest method\n */\ninterface AuthService {\n handleRequest(request: Request): Promise<Response>;\n}\n\n/**\n * @deprecated Use `HonoServerPlugin` + `createRestApiPlugin()` + `createDispatcherPlugin()` instead.\n * This function bundles all routes into a single Hono app using the legacy HttpDispatcher.\n * The plugin-based approach provides better modularity and separation of concerns.\n *\n * Migration:\n * ```ts\n * // Before:\n * const app = createHonoApp({ kernel, prefix: '/api/v1' });\n *\n * // After:\n * import { createRestApiPlugin } from '@objectstack/rest';\n * import { createDispatcherPlugin } from '@objectstack/runtime';\n * kernel.use(new HonoServerPlugin({ port: 3000 }));\n * kernel.use(createRestApiPlugin());\n * kernel.use(createDispatcherPlugin({ prefix: '/api/v1' }));\n * ```\n */\nexport function createHonoApp(options: ObjectStackHonoOptions) {\n const app = new Hono();\n const { prefix = '/api' } = options;\n const dispatcher = new HttpDispatcher(options.kernel);\n\n app.use('*', cors());\n\n // --- Helper for Response Normalization ---\n const normalizeResponse = (c: any, result: HttpDispatcherResult) => {\n if (result.handled) {\n if (result.response) {\n return c.json(result.response.body, result.response.status as any, result.response.headers);\n }\n if (result.result) {\n const res = result.result;\n // Redirect\n if (res.type === 'redirect' && res.url) {\n return c.redirect(res.url);\n }\n // Stream\n if (res.type === 'stream' && res.stream) {\n return c.body(res.stream, 200, res.headers);\n }\n \n // Hono handles standard Response objects\n return res;\n }\n }\n return c.json({ success: false, error: { message: 'Not Found', code: 404 } }, 404);\n }\n\n // --- 0. Discovery Endpoint ---\n app.get(prefix, (c) => {\n return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });\n });\n\n // --- 1. Auth ---\n app.all(`${prefix}/auth/*`, async (c) => {\n try {\n // Try AuthPlugin service first (preferred path)\n const authService = typeof options.kernel.getService === 'function'\n ? options.kernel.getService<AuthService>('auth')\n : null;\n\n if (authService && typeof authService.handleRequest === 'function') {\n const response = await authService.handleRequest(c.req.raw);\n return response;\n }\n\n // Fallback to legacy dispatcher\n const path = c.req.path.substring(c.req.path.indexOf('/auth/') + 6);\n const body = await c.req.parseBody().catch(() => ({})); \n \n const result = await dispatcher.handleAuth(path, c.req.method, body, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 2. GraphQL ---\n app.post(`${prefix}/graphql`, async (c) => {\n try {\n const body = await c.req.json();\n const result = await dispatcher.handleGraphQL(body, { request: c.req.raw });\n return c.json(result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 3. Metadata Endpoints ---\n app.all(`${prefix}/meta*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/meta') + 5);\n const method = c.req.method;\n let body = undefined;\n \n if (method === 'PUT' || method === 'POST') {\n // Attempt to parse JSON body\n try {\n body = await c.req.json();\n } catch (e) {\n // Ignore parse errors, body remains undefined or empty\n body = {};\n }\n }\n const query = c.req.query();\n\n const result = await dispatcher.handleMetadata(path, { request: c.req.raw }, method, body, query);\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 4. Data Endpoints ---\n app.all(`${prefix}/data*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/data') + 5);\n const method = c.req.method;\n \n let body = {};\n if (method === 'POST' || method === 'PATCH') {\n body = await c.req.json().catch(() => ({}));\n }\n const query = c.req.query();\n\n const result = await dispatcher.handleData(path, method, body, query, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 5. Analytics Endpoints ---\n app.all(`${prefix}/analytics*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/analytics') + 10);\n const method = c.req.method;\n \n let body = {};\n if (method === 'POST') {\n body = await c.req.json().catch(() => ({}));\n }\n\n const result = await dispatcher.handleAnalytics(path, method, body, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 7. Automation Endpoints ---\n app.all(`${prefix}/automation*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/automation') + 11);\n const method = c.req.method;\n \n let body = {};\n if (method === 'POST') {\n body = await c.req.json().catch(() => ({}));\n }\n\n const result = await dispatcher.handleAutomation(path, method, body, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 8. Storage Endpoints ---\n app.all(`${prefix}/storage*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/storage') + 8);\n const method = c.req.method;\n \n let file: any = undefined;\n if (method === 'POST' && path.includes('upload')) {\n const body = await c.req.parseBody();\n file = body['file'];\n }\n\n const result = await dispatcher.handleStorage(path, method, file, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 9. Package Management Endpoints ---\n app.all(`${prefix}/packages*`, async (c) => {\n try {\n const packagesIndex = c.req.path.indexOf('/packages');\n const path = c.req.path.substring(packagesIndex + 9); // length of '/packages'\n const method = c.req.method;\n\n let body = {};\n if (method === 'POST' || method === 'PATCH' || method === 'PUT') {\n body = await c.req.json().catch(() => ({}));\n }\n const query = c.req.query();\n\n const result = await dispatcher.handlePackages(path, method, body, query, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n return app;\n}\n\n/**\n * Middleware mode for existing Hono apps\n */\nexport function objectStackMiddleware(kernel: ObjectKernel) {\n return async (c: any, next: any) => {\n c.set('objectStack', kernel);\n await next();\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { type ObjectKernel } from '@objectstack/runtime';\n\nexport interface ObjectStackHonoOptions {\n kernel: ObjectKernel;\n prefix?: string;\n}\n\n/**\n * Middleware mode for existing Hono apps\n */\nexport function objectStackMiddleware(kernel: ObjectKernel) {\n return async (c: any, next: any) => {\n c.set('objectStack', kernel);\n await next();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAYO,SAAS,sBAAsB,QAAsB;AAC1D,SAAO,OAAO,GAAQ,SAAc;AAClC,MAAE,IAAI,eAAe,MAAM;AAC3B,UAAM,KAAK;AAAA,EACb;AACF;","names":[]}
package/dist/index.mjs CHANGED
@@ -1,152 +1,4 @@
1
1
  // src/index.ts
2
- import { Hono } from "hono";
3
- import { cors } from "hono/cors";
4
- import { HttpDispatcher } from "@objectstack/runtime";
5
- function createHonoApp(options) {
6
- const app = new Hono();
7
- const { prefix = "/api" } = options;
8
- const dispatcher = new HttpDispatcher(options.kernel);
9
- app.use("*", cors());
10
- const normalizeResponse = (c, result) => {
11
- if (result.handled) {
12
- if (result.response) {
13
- return c.json(result.response.body, result.response.status, result.response.headers);
14
- }
15
- if (result.result) {
16
- const res = result.result;
17
- if (res.type === "redirect" && res.url) {
18
- return c.redirect(res.url);
19
- }
20
- if (res.type === "stream" && res.stream) {
21
- return c.body(res.stream, 200, res.headers);
22
- }
23
- return res;
24
- }
25
- }
26
- return c.json({ success: false, error: { message: "Not Found", code: 404 } }, 404);
27
- };
28
- app.get(prefix, (c) => {
29
- return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });
30
- });
31
- app.all(`${prefix}/auth/*`, async (c) => {
32
- try {
33
- const authService = typeof options.kernel.getService === "function" ? options.kernel.getService("auth") : null;
34
- if (authService && typeof authService.handleRequest === "function") {
35
- const response = await authService.handleRequest(c.req.raw);
36
- return response;
37
- }
38
- const path = c.req.path.substring(c.req.path.indexOf("/auth/") + 6);
39
- const body = await c.req.parseBody().catch(() => ({}));
40
- const result = await dispatcher.handleAuth(path, c.req.method, body, { request: c.req.raw });
41
- return normalizeResponse(c, result);
42
- } catch (err) {
43
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
44
- }
45
- });
46
- app.post(`${prefix}/graphql`, async (c) => {
47
- try {
48
- const body = await c.req.json();
49
- const result = await dispatcher.handleGraphQL(body, { request: c.req.raw });
50
- return c.json(result);
51
- } catch (err) {
52
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
53
- }
54
- });
55
- app.all(`${prefix}/meta*`, async (c) => {
56
- try {
57
- const path = c.req.path.substring(c.req.path.indexOf("/meta") + 5);
58
- const method = c.req.method;
59
- let body = void 0;
60
- if (method === "PUT" || method === "POST") {
61
- try {
62
- body = await c.req.json();
63
- } catch (e) {
64
- body = {};
65
- }
66
- }
67
- const query = c.req.query();
68
- const result = await dispatcher.handleMetadata(path, { request: c.req.raw }, method, body, query);
69
- return normalizeResponse(c, result);
70
- } catch (err) {
71
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
72
- }
73
- });
74
- app.all(`${prefix}/data*`, async (c) => {
75
- try {
76
- const path = c.req.path.substring(c.req.path.indexOf("/data") + 5);
77
- const method = c.req.method;
78
- let body = {};
79
- if (method === "POST" || method === "PATCH") {
80
- body = await c.req.json().catch(() => ({}));
81
- }
82
- const query = c.req.query();
83
- const result = await dispatcher.handleData(path, method, body, query, { request: c.req.raw });
84
- return normalizeResponse(c, result);
85
- } catch (err) {
86
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
87
- }
88
- });
89
- app.all(`${prefix}/analytics*`, async (c) => {
90
- try {
91
- const path = c.req.path.substring(c.req.path.indexOf("/analytics") + 10);
92
- const method = c.req.method;
93
- let body = {};
94
- if (method === "POST") {
95
- body = await c.req.json().catch(() => ({}));
96
- }
97
- const result = await dispatcher.handleAnalytics(path, method, body, { request: c.req.raw });
98
- return normalizeResponse(c, result);
99
- } catch (err) {
100
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
101
- }
102
- });
103
- app.all(`${prefix}/automation*`, async (c) => {
104
- try {
105
- const path = c.req.path.substring(c.req.path.indexOf("/automation") + 11);
106
- const method = c.req.method;
107
- let body = {};
108
- if (method === "POST") {
109
- body = await c.req.json().catch(() => ({}));
110
- }
111
- const result = await dispatcher.handleAutomation(path, method, body, { request: c.req.raw });
112
- return normalizeResponse(c, result);
113
- } catch (err) {
114
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
115
- }
116
- });
117
- app.all(`${prefix}/storage*`, async (c) => {
118
- try {
119
- const path = c.req.path.substring(c.req.path.indexOf("/storage") + 8);
120
- const method = c.req.method;
121
- let file = void 0;
122
- if (method === "POST" && path.includes("upload")) {
123
- const body = await c.req.parseBody();
124
- file = body["file"];
125
- }
126
- const result = await dispatcher.handleStorage(path, method, file, { request: c.req.raw });
127
- return normalizeResponse(c, result);
128
- } catch (err) {
129
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
130
- }
131
- });
132
- app.all(`${prefix}/packages*`, async (c) => {
133
- try {
134
- const packagesIndex = c.req.path.indexOf("/packages");
135
- const path = c.req.path.substring(packagesIndex + 9);
136
- const method = c.req.method;
137
- let body = {};
138
- if (method === "POST" || method === "PATCH" || method === "PUT") {
139
- body = await c.req.json().catch(() => ({}));
140
- }
141
- const query = c.req.query();
142
- const result = await dispatcher.handlePackages(path, method, body, query, { request: c.req.raw });
143
- return normalizeResponse(c, result);
144
- } catch (err) {
145
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
146
- }
147
- });
148
- return app;
149
- }
150
2
  function objectStackMiddleware(kernel) {
151
3
  return async (c, next) => {
152
4
  c.set("objectStack", kernel);
@@ -154,7 +6,6 @@ function objectStackMiddleware(kernel) {
154
6
  };
155
7
  }
156
8
  export {
157
- createHonoApp,
158
9
  objectStackMiddleware
159
10
  };
160
11
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';\n\nexport interface ObjectStackHonoOptions {\n kernel: ObjectKernel;\n prefix?: string;\n}\n\n/**\n * Auth service interface with handleRequest method\n */\ninterface AuthService {\n handleRequest(request: Request): Promise<Response>;\n}\n\n/**\n * @deprecated Use `HonoServerPlugin` + `createRestApiPlugin()` + `createDispatcherPlugin()` instead.\n * This function bundles all routes into a single Hono app using the legacy HttpDispatcher.\n * The plugin-based approach provides better modularity and separation of concerns.\n *\n * Migration:\n * ```ts\n * // Before:\n * const app = createHonoApp({ kernel, prefix: '/api/v1' });\n *\n * // After:\n * import { createRestApiPlugin } from '@objectstack/rest';\n * import { createDispatcherPlugin } from '@objectstack/runtime';\n * kernel.use(new HonoServerPlugin({ port: 3000 }));\n * kernel.use(createRestApiPlugin());\n * kernel.use(createDispatcherPlugin({ prefix: '/api/v1' }));\n * ```\n */\nexport function createHonoApp(options: ObjectStackHonoOptions) {\n const app = new Hono();\n const { prefix = '/api' } = options;\n const dispatcher = new HttpDispatcher(options.kernel);\n\n app.use('*', cors());\n\n // --- Helper for Response Normalization ---\n const normalizeResponse = (c: any, result: HttpDispatcherResult) => {\n if (result.handled) {\n if (result.response) {\n return c.json(result.response.body, result.response.status as any, result.response.headers);\n }\n if (result.result) {\n const res = result.result;\n // Redirect\n if (res.type === 'redirect' && res.url) {\n return c.redirect(res.url);\n }\n // Stream\n if (res.type === 'stream' && res.stream) {\n return c.body(res.stream, 200, res.headers);\n }\n \n // Hono handles standard Response objects\n return res;\n }\n }\n return c.json({ success: false, error: { message: 'Not Found', code: 404 } }, 404);\n }\n\n // --- 0. Discovery Endpoint ---\n app.get(prefix, (c) => {\n return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });\n });\n\n // --- 1. Auth ---\n app.all(`${prefix}/auth/*`, async (c) => {\n try {\n // Try AuthPlugin service first (preferred path)\n const authService = typeof options.kernel.getService === 'function'\n ? options.kernel.getService<AuthService>('auth')\n : null;\n\n if (authService && typeof authService.handleRequest === 'function') {\n const response = await authService.handleRequest(c.req.raw);\n return response;\n }\n\n // Fallback to legacy dispatcher\n const path = c.req.path.substring(c.req.path.indexOf('/auth/') + 6);\n const body = await c.req.parseBody().catch(() => ({})); \n \n const result = await dispatcher.handleAuth(path, c.req.method, body, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 2. GraphQL ---\n app.post(`${prefix}/graphql`, async (c) => {\n try {\n const body = await c.req.json();\n const result = await dispatcher.handleGraphQL(body, { request: c.req.raw });\n return c.json(result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 3. Metadata Endpoints ---\n app.all(`${prefix}/meta*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/meta') + 5);\n const method = c.req.method;\n let body = undefined;\n \n if (method === 'PUT' || method === 'POST') {\n // Attempt to parse JSON body\n try {\n body = await c.req.json();\n } catch (e) {\n // Ignore parse errors, body remains undefined or empty\n body = {};\n }\n }\n const query = c.req.query();\n\n const result = await dispatcher.handleMetadata(path, { request: c.req.raw }, method, body, query);\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 4. Data Endpoints ---\n app.all(`${prefix}/data*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/data') + 5);\n const method = c.req.method;\n \n let body = {};\n if (method === 'POST' || method === 'PATCH') {\n body = await c.req.json().catch(() => ({}));\n }\n const query = c.req.query();\n\n const result = await dispatcher.handleData(path, method, body, query, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 5. Analytics Endpoints ---\n app.all(`${prefix}/analytics*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/analytics') + 10);\n const method = c.req.method;\n \n let body = {};\n if (method === 'POST') {\n body = await c.req.json().catch(() => ({}));\n }\n\n const result = await dispatcher.handleAnalytics(path, method, body, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 7. Automation Endpoints ---\n app.all(`${prefix}/automation*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/automation') + 11);\n const method = c.req.method;\n \n let body = {};\n if (method === 'POST') {\n body = await c.req.json().catch(() => ({}));\n }\n\n const result = await dispatcher.handleAutomation(path, method, body, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 8. Storage Endpoints ---\n app.all(`${prefix}/storage*`, async (c) => {\n try {\n const path = c.req.path.substring(c.req.path.indexOf('/storage') + 8);\n const method = c.req.method;\n \n let file: any = undefined;\n if (method === 'POST' && path.includes('upload')) {\n const body = await c.req.parseBody();\n file = body['file'];\n }\n\n const result = await dispatcher.handleStorage(path, method, file, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n // --- 9. Package Management Endpoints ---\n app.all(`${prefix}/packages*`, async (c) => {\n try {\n const packagesIndex = c.req.path.indexOf('/packages');\n const path = c.req.path.substring(packagesIndex + 9); // length of '/packages'\n const method = c.req.method;\n\n let body = {};\n if (method === 'POST' || method === 'PATCH' || method === 'PUT') {\n body = await c.req.json().catch(() => ({}));\n }\n const query = c.req.query();\n\n const result = await dispatcher.handlePackages(path, method, body, query, { request: c.req.raw });\n return normalizeResponse(c, result);\n } catch (err: any) {\n return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);\n }\n });\n\n return app;\n}\n\n/**\n * Middleware mode for existing Hono apps\n */\nexport function objectStackMiddleware(kernel: ObjectKernel) {\n return async (c: any, next: any) => {\n c.set('objectStack', kernel);\n await next();\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { type ObjectKernel } from '@objectstack/runtime';\n\nexport interface ObjectStackHonoOptions {\n kernel: ObjectKernel;\n prefix?: string;\n}\n\n/**\n * Middleware mode for existing Hono apps\n */\nexport function objectStackMiddleware(kernel: ObjectKernel) {\n return async (c: any, next: any) => {\n c.set('objectStack', kernel);\n await next();\n };\n}\n"],"mappings":";AAYO,SAAS,sBAAsB,QAAsB;AAC1D,SAAO,OAAO,GAAQ,SAAc;AAClC,MAAE,IAAI,eAAe,MAAM;AAC3B,UAAM,KAAK;AAAA,EACb;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/hono",
3
- "version": "2.0.7",
3
+ "version": "3.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,13 +13,13 @@
13
13
  },
14
14
  "peerDependencies": {
15
15
  "hono": "^4.11.9",
16
- "@objectstack/runtime": "2.0.7"
16
+ "@objectstack/runtime": "3.0.0"
17
17
  },
18
18
  "devDependencies": {
19
19
  "hono": "^4.11.9",
20
20
  "typescript": "^5.0.0",
21
21
  "vitest": "^4.0.18",
22
- "@objectstack/runtime": "2.0.7"
22
+ "@objectstack/runtime": "3.0.0"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup --config ../../../tsup.config.ts",
package/src/hono.test.ts CHANGED
@@ -3,400 +3,15 @@
3
3
  import { describe, it, expect, vi, beforeEach } from 'vitest';
4
4
  import { Hono } from 'hono';
5
5
 
6
- // Mock dispatcher instance
7
- const mockDispatcher = {
8
- getDiscoveryInfo: vi.fn().mockReturnValue({ version: '1.0', endpoints: [] }),
9
- handleAuth: vi.fn().mockResolvedValue({ handled: true, response: { body: { ok: true }, status: 200 } }),
10
- handleGraphQL: vi.fn().mockResolvedValue({ data: {} }),
11
- handleMetadata: vi.fn().mockResolvedValue({ handled: true, response: { body: { objects: [] }, status: 200 } }),
12
- handleData: vi.fn().mockResolvedValue({ handled: true, response: { body: { records: [] }, status: 200 } }),
13
- handleAnalytics: vi.fn().mockResolvedValue({ handled: true, response: { body: {}, status: 200 } }),
14
- handleAutomation: vi.fn().mockResolvedValue({ handled: true, response: { body: {}, status: 200 } }),
15
- handleStorage: vi.fn().mockResolvedValue({ handled: true, response: { body: {}, status: 200 } }),
16
- handlePackages: vi.fn().mockResolvedValue({ handled: true, response: { body: {}, status: 200 } }),
17
- };
18
-
19
- vi.mock('@objectstack/runtime', () => {
20
- return {
21
- HttpDispatcher: function HttpDispatcher() {
22
- return mockDispatcher;
23
- },
24
- };
25
- });
26
-
27
- import { createHonoApp, objectStackMiddleware } from './index';
6
+ import { objectStackMiddleware } from './index';
28
7
 
29
8
  const mockKernel = { name: 'test-kernel' } as any;
30
9
 
31
- describe('createHonoApp', () => {
10
+ describe('objectStackMiddleware', () => {
32
11
  beforeEach(() => {
33
12
  vi.clearAllMocks();
34
13
  });
35
14
 
36
- it('should return a Hono app instance', () => {
37
- const app = createHonoApp({ kernel: mockKernel });
38
- expect(app).toBeInstanceOf(Hono);
39
- });
40
-
41
- describe('Discovery Endpoint', () => {
42
- it('GET /api returns discovery info', async () => {
43
- const app = createHonoApp({ kernel: mockKernel });
44
- const res = await app.request('/api');
45
- expect(res.status).toBe(200);
46
- const json = await res.json();
47
- expect(json.data).toEqual({ version: '1.0', endpoints: [] });
48
- expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api');
49
- });
50
-
51
- it('uses custom prefix for discovery', async () => {
52
- const app = createHonoApp({ kernel: mockKernel, prefix: '/v2' });
53
- const res = await app.request('/v2');
54
- expect(res.status).toBe(200);
55
- expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/v2');
56
- });
57
- });
58
-
59
- describe('Auth Endpoint', () => {
60
- it('POST /api/auth/login calls handleAuth', async () => {
61
- const app = createHonoApp({ kernel: mockKernel });
62
- const res = await app.request('/api/auth/login', { method: 'POST' });
63
- expect(res.status).toBe(200);
64
- expect(mockDispatcher.handleAuth).toHaveBeenCalledWith(
65
- 'login',
66
- 'POST',
67
- expect.anything(),
68
- expect.objectContaining({ request: expect.any(Request) }),
69
- );
70
- });
71
-
72
- it('GET /api/auth/callback calls handleAuth', async () => {
73
- const app = createHonoApp({ kernel: mockKernel });
74
- const res = await app.request('/api/auth/callback', { method: 'GET' });
75
- expect(res.status).toBe(200);
76
- expect(mockDispatcher.handleAuth).toHaveBeenCalledWith(
77
- 'callback',
78
- 'GET',
79
- expect.anything(),
80
- expect.objectContaining({ request: expect.any(Request) }),
81
- );
82
- });
83
-
84
- it('returns error on handleAuth exception', async () => {
85
- mockDispatcher.handleAuth.mockRejectedValueOnce(
86
- Object.assign(new Error('Unauthorized'), { statusCode: 401 }),
87
- );
88
- const app = createHonoApp({ kernel: mockKernel });
89
- const res = await app.request('/api/auth/login', { method: 'POST' });
90
- expect(res.status).toBe(401);
91
- const json = await res.json();
92
- expect(json.success).toBe(false);
93
- expect(json.error.message).toBe('Unauthorized');
94
- });
95
- });
96
-
97
- describe('Auth via AuthPlugin service', () => {
98
- it('uses kernel.getService("auth") when available', async () => {
99
- const mockHandleRequest = vi.fn().mockResolvedValue(
100
- new Response(JSON.stringify({ user: { id: '1' } }), {
101
- status: 200,
102
- headers: { 'Content-Type': 'application/json' },
103
- }),
104
- );
105
- const kernelWithAuth = {
106
- ...mockKernel,
107
- getService: vi.fn().mockReturnValue({ handleRequest: mockHandleRequest }),
108
- };
109
- const app = createHonoApp({ kernel: kernelWithAuth });
110
- const res = await app.request('/api/auth/sign-in/email', {
111
- method: 'POST',
112
- headers: { 'Content-Type': 'application/json' },
113
- body: JSON.stringify({ email: 'a@b.com', password: 'pass' }),
114
- });
115
- expect(res.status).toBe(200);
116
- const json = await res.json();
117
- expect(json.user.id).toBe('1');
118
- expect(kernelWithAuth.getService).toHaveBeenCalledWith('auth');
119
- expect(mockHandleRequest).toHaveBeenCalledWith(expect.any(Request));
120
- expect(mockDispatcher.handleAuth).not.toHaveBeenCalled();
121
- });
122
-
123
- it('falls back to dispatcher.handleAuth when auth service is not available', async () => {
124
- const kernelWithoutAuth = {
125
- ...mockKernel,
126
- getService: vi.fn().mockReturnValue(null),
127
- };
128
- const app = createHonoApp({ kernel: kernelWithoutAuth });
129
- const res = await app.request('/api/auth/login', { method: 'POST' });
130
- expect(res.status).toBe(200);
131
- expect(mockDispatcher.handleAuth).toHaveBeenCalled();
132
- });
133
-
134
- it('forwards GET requests to auth service', async () => {
135
- const mockHandleRequest = vi.fn().mockResolvedValue(
136
- new Response(JSON.stringify({ session: { token: 'abc' } }), {
137
- status: 200,
138
- headers: { 'Content-Type': 'application/json' },
139
- }),
140
- );
141
- const kernelWithAuth = {
142
- ...mockKernel,
143
- getService: vi.fn().mockReturnValue({ handleRequest: mockHandleRequest }),
144
- };
145
- const app = createHonoApp({ kernel: kernelWithAuth });
146
- const res = await app.request('/api/auth/get-session', { method: 'GET' });
147
- expect(res.status).toBe(200);
148
- const json = await res.json();
149
- expect(json.session.token).toBe('abc');
150
- expect(mockHandleRequest).toHaveBeenCalled();
151
- });
152
-
153
- it('returns error when auth service throws', async () => {
154
- const mockHandleRequest = vi.fn().mockRejectedValue(new Error('Auth failed'));
155
- const kernelWithAuth = {
156
- ...mockKernel,
157
- getService: vi.fn().mockReturnValue({ handleRequest: mockHandleRequest }),
158
- };
159
- const app = createHonoApp({ kernel: kernelWithAuth });
160
- const res = await app.request('/api/auth/sign-in/email', { method: 'POST' });
161
- expect(res.status).toBe(500);
162
- const json = await res.json();
163
- expect(json.success).toBe(false);
164
- expect(json.error.message).toBe('Auth failed');
165
- });
166
- });
167
-
168
- describe('GraphQL Endpoint', () => {
169
- it('POST /api/graphql calls handleGraphQL', async () => {
170
- const app = createHonoApp({ kernel: mockKernel });
171
- const body = { query: '{ objects { name } }' };
172
- const res = await app.request('/api/graphql', {
173
- method: 'POST',
174
- headers: { 'Content-Type': 'application/json' },
175
- body: JSON.stringify(body),
176
- });
177
- expect(res.status).toBe(200);
178
- expect(mockDispatcher.handleGraphQL).toHaveBeenCalledWith(
179
- body,
180
- expect.objectContaining({ request: expect.any(Request) }),
181
- );
182
- });
183
-
184
- it('returns error on handleGraphQL exception', async () => {
185
- mockDispatcher.handleGraphQL.mockRejectedValueOnce(new Error('Parse error'));
186
- const app = createHonoApp({ kernel: mockKernel });
187
- const res = await app.request('/api/graphql', {
188
- method: 'POST',
189
- headers: { 'Content-Type': 'application/json' },
190
- body: JSON.stringify({ query: 'bad' }),
191
- });
192
- expect(res.status).toBe(500);
193
- const json = await res.json();
194
- expect(json.success).toBe(false);
195
- });
196
- });
197
-
198
- describe('Metadata Endpoint', () => {
199
- it('GET /api/meta/objects calls handleMetadata', async () => {
200
- const app = createHonoApp({ kernel: mockKernel });
201
- const res = await app.request('/api/meta/objects');
202
- expect(res.status).toBe(200);
203
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
204
- '/objects',
205
- expect.objectContaining({ request: expect.any(Request) }),
206
- 'GET',
207
- undefined,
208
- expect.any(Object),
209
- );
210
- });
211
-
212
- it('PUT /api/meta/objects parses JSON body', async () => {
213
- const app = createHonoApp({ kernel: mockKernel });
214
- const body = { name: 'test_object' };
215
- const res = await app.request('/api/meta/objects', {
216
- method: 'PUT',
217
- headers: { 'Content-Type': 'application/json' },
218
- body: JSON.stringify(body),
219
- });
220
- expect(res.status).toBe(200);
221
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
222
- '/objects',
223
- expect.objectContaining({ request: expect.any(Request) }),
224
- 'PUT',
225
- body,
226
- expect.any(Object),
227
- );
228
- });
229
- });
230
-
231
- describe('Data Endpoint', () => {
232
- it('GET /api/data/account calls handleData', async () => {
233
- const app = createHonoApp({ kernel: mockKernel });
234
- const res = await app.request('/api/data/account');
235
- expect(res.status).toBe(200);
236
- expect(mockDispatcher.handleData).toHaveBeenCalledWith(
237
- '/account',
238
- 'GET',
239
- {},
240
- expect.any(Object),
241
- expect.objectContaining({ request: expect.any(Request) }),
242
- );
243
- });
244
-
245
- it('POST /api/data/account parses JSON body', async () => {
246
- const app = createHonoApp({ kernel: mockKernel });
247
- const body = { name: 'Acme' };
248
- const res = await app.request('/api/data/account', {
249
- method: 'POST',
250
- headers: { 'Content-Type': 'application/json' },
251
- body: JSON.stringify(body),
252
- });
253
- expect(res.status).toBe(200);
254
- expect(mockDispatcher.handleData).toHaveBeenCalledWith(
255
- '/account',
256
- 'POST',
257
- body,
258
- expect.any(Object),
259
- expect.objectContaining({ request: expect.any(Request) }),
260
- );
261
- });
262
-
263
- it('returns 404 when result is not handled', async () => {
264
- mockDispatcher.handleData.mockResolvedValueOnce({ handled: false });
265
- const app = createHonoApp({ kernel: mockKernel });
266
- const res = await app.request('/api/data/missing');
267
- expect(res.status).toBe(404);
268
- const json = await res.json();
269
- expect(json.success).toBe(false);
270
- });
271
- });
272
-
273
- describe('Analytics Endpoint', () => {
274
- it('GET /api/analytics/report calls handleAnalytics', async () => {
275
- const app = createHonoApp({ kernel: mockKernel });
276
- const res = await app.request('/api/analytics/report');
277
- expect(res.status).toBe(200);
278
- expect(mockDispatcher.handleAnalytics).toHaveBeenCalled();
279
- });
280
-
281
- it('POST /api/analytics/report parses body', async () => {
282
- const app = createHonoApp({ kernel: mockKernel });
283
- const body = { metric: 'revenue' };
284
- const res = await app.request('/api/analytics/report', {
285
- method: 'POST',
286
- headers: { 'Content-Type': 'application/json' },
287
- body: JSON.stringify(body),
288
- });
289
- expect(res.status).toBe(200);
290
- expect(mockDispatcher.handleAnalytics).toHaveBeenCalledWith(
291
- '/report',
292
- 'POST',
293
- body,
294
- expect.objectContaining({ request: expect.any(Request) }),
295
- );
296
- });
297
- });
298
-
299
- describe('Automation Endpoint', () => {
300
- it('GET /api/automation/flows calls handleAutomation', async () => {
301
- const app = createHonoApp({ kernel: mockKernel });
302
- const res = await app.request('/api/automation/flows');
303
- expect(res.status).toBe(200);
304
- expect(mockDispatcher.handleAutomation).toHaveBeenCalled();
305
- });
306
-
307
- it('POST /api/automation/flows parses body', async () => {
308
- const app = createHonoApp({ kernel: mockKernel });
309
- const body = { trigger: 'on_create' };
310
- const res = await app.request('/api/automation/flows', {
311
- method: 'POST',
312
- headers: { 'Content-Type': 'application/json' },
313
- body: JSON.stringify(body),
314
- });
315
- expect(res.status).toBe(200);
316
- expect(mockDispatcher.handleAutomation).toHaveBeenCalledWith(
317
- '/flows',
318
- 'POST',
319
- body,
320
- expect.objectContaining({ request: expect.any(Request) }),
321
- );
322
- });
323
- });
324
-
325
- describe('Storage Endpoint', () => {
326
- it('GET /api/storage/files calls handleStorage', async () => {
327
- const app = createHonoApp({ kernel: mockKernel });
328
- const res = await app.request('/api/storage/files');
329
- expect(res.status).toBe(200);
330
- expect(mockDispatcher.handleStorage).toHaveBeenCalled();
331
- });
332
- });
333
-
334
- describe('Packages Endpoint', () => {
335
- it('GET /api/packages calls handlePackages', async () => {
336
- const app = createHonoApp({ kernel: mockKernel });
337
- const res = await app.request('/api/packages');
338
- expect(res.status).toBe(200);
339
- expect(mockDispatcher.handlePackages).toHaveBeenCalledWith(
340
- '',
341
- 'GET',
342
- {},
343
- expect.any(Object),
344
- expect.objectContaining({ request: expect.any(Request) }),
345
- );
346
- });
347
-
348
- it('POST /api/packages/install parses body', async () => {
349
- const app = createHonoApp({ kernel: mockKernel });
350
- const body = { package: 'my-plugin' };
351
- const res = await app.request('/api/packages/install', {
352
- method: 'POST',
353
- headers: { 'Content-Type': 'application/json' },
354
- body: JSON.stringify(body),
355
- });
356
- expect(res.status).toBe(200);
357
- expect(mockDispatcher.handlePackages).toHaveBeenCalledWith(
358
- '/install',
359
- 'POST',
360
- body,
361
- expect.any(Object),
362
- expect.objectContaining({ request: expect.any(Request) }),
363
- );
364
- });
365
- });
366
-
367
- describe('normalizeResponse', () => {
368
- it('handles redirect result', async () => {
369
- mockDispatcher.handleData.mockResolvedValueOnce({
370
- handled: true,
371
- result: { type: 'redirect', url: 'https://example.com' },
372
- });
373
- const app = createHonoApp({ kernel: mockKernel });
374
- const res = await app.request('/api/data/redir', { redirect: 'manual' });
375
- expect(res.status).toBe(302);
376
- expect(res.headers.get('location')).toBe('https://example.com');
377
- });
378
-
379
- it('handles stream result', async () => {
380
- const stream = new ReadableStream({
381
- start(controller) {
382
- controller.enqueue(new TextEncoder().encode('hello'));
383
- controller.close();
384
- },
385
- });
386
- mockDispatcher.handleData.mockResolvedValueOnce({
387
- handled: true,
388
- result: { type: 'stream', stream, headers: { 'Content-Type': 'text/plain' } },
389
- });
390
- const app = createHonoApp({ kernel: mockKernel });
391
- const res = await app.request('/api/data/stream');
392
- expect(res.status).toBe(200);
393
- const text = await res.text();
394
- expect(text).toBe('hello');
395
- });
396
- });
397
- });
398
-
399
- describe('objectStackMiddleware', () => {
400
15
  it('sets kernel on context via c.set', async () => {
401
16
  const app = new Hono();
402
17
  const middleware = objectStackMiddleware(mockKernel);
@@ -429,4 +44,20 @@ describe('objectStackMiddleware', () => {
429
44
  expect(res.status).toBe(200);
430
45
  expect(spy).toHaveBeenCalled();
431
46
  });
47
+
48
+ it('provides the correct kernel instance', async () => {
49
+ const app = new Hono();
50
+ const middleware = objectStackMiddleware(mockKernel);
51
+
52
+ app.use('*', middleware);
53
+ app.get('/test', (c) => {
54
+ const kernel = c.get('objectStack');
55
+ return c.json({ name: kernel.name });
56
+ });
57
+
58
+ const res = await app.request('/test');
59
+ expect(res.status).toBe(200);
60
+ const json = await res.json();
61
+ expect(json.name).toBe('test-kernel');
62
+ });
432
63
  });
package/src/index.ts CHANGED
@@ -1,232 +1,12 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
- import { Hono } from 'hono';
4
- import { cors } from 'hono/cors';
5
- import { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';
3
+ import { type ObjectKernel } from '@objectstack/runtime';
6
4
 
7
5
  export interface ObjectStackHonoOptions {
8
6
  kernel: ObjectKernel;
9
7
  prefix?: string;
10
8
  }
11
9
 
12
- /**
13
- * Auth service interface with handleRequest method
14
- */
15
- interface AuthService {
16
- handleRequest(request: Request): Promise<Response>;
17
- }
18
-
19
- /**
20
- * @deprecated Use `HonoServerPlugin` + `createRestApiPlugin()` + `createDispatcherPlugin()` instead.
21
- * This function bundles all routes into a single Hono app using the legacy HttpDispatcher.
22
- * The plugin-based approach provides better modularity and separation of concerns.
23
- *
24
- * Migration:
25
- * ```ts
26
- * // Before:
27
- * const app = createHonoApp({ kernel, prefix: '/api/v1' });
28
- *
29
- * // After:
30
- * import { createRestApiPlugin } from '@objectstack/rest';
31
- * import { createDispatcherPlugin } from '@objectstack/runtime';
32
- * kernel.use(new HonoServerPlugin({ port: 3000 }));
33
- * kernel.use(createRestApiPlugin());
34
- * kernel.use(createDispatcherPlugin({ prefix: '/api/v1' }));
35
- * ```
36
- */
37
- export function createHonoApp(options: ObjectStackHonoOptions) {
38
- const app = new Hono();
39
- const { prefix = '/api' } = options;
40
- const dispatcher = new HttpDispatcher(options.kernel);
41
-
42
- app.use('*', cors());
43
-
44
- // --- Helper for Response Normalization ---
45
- const normalizeResponse = (c: any, result: HttpDispatcherResult) => {
46
- if (result.handled) {
47
- if (result.response) {
48
- return c.json(result.response.body, result.response.status as any, result.response.headers);
49
- }
50
- if (result.result) {
51
- const res = result.result;
52
- // Redirect
53
- if (res.type === 'redirect' && res.url) {
54
- return c.redirect(res.url);
55
- }
56
- // Stream
57
- if (res.type === 'stream' && res.stream) {
58
- return c.body(res.stream, 200, res.headers);
59
- }
60
-
61
- // Hono handles standard Response objects
62
- return res;
63
- }
64
- }
65
- return c.json({ success: false, error: { message: 'Not Found', code: 404 } }, 404);
66
- }
67
-
68
- // --- 0. Discovery Endpoint ---
69
- app.get(prefix, (c) => {
70
- return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });
71
- });
72
-
73
- // --- 1. Auth ---
74
- app.all(`${prefix}/auth/*`, async (c) => {
75
- try {
76
- // Try AuthPlugin service first (preferred path)
77
- const authService = typeof options.kernel.getService === 'function'
78
- ? options.kernel.getService<AuthService>('auth')
79
- : null;
80
-
81
- if (authService && typeof authService.handleRequest === 'function') {
82
- const response = await authService.handleRequest(c.req.raw);
83
- return response;
84
- }
85
-
86
- // Fallback to legacy dispatcher
87
- const path = c.req.path.substring(c.req.path.indexOf('/auth/') + 6);
88
- const body = await c.req.parseBody().catch(() => ({}));
89
-
90
- const result = await dispatcher.handleAuth(path, c.req.method, body, { request: c.req.raw });
91
- return normalizeResponse(c, result);
92
- } catch (err: any) {
93
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
94
- }
95
- });
96
-
97
- // --- 2. GraphQL ---
98
- app.post(`${prefix}/graphql`, async (c) => {
99
- try {
100
- const body = await c.req.json();
101
- const result = await dispatcher.handleGraphQL(body, { request: c.req.raw });
102
- return c.json(result);
103
- } catch (err: any) {
104
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
105
- }
106
- });
107
-
108
- // --- 3. Metadata Endpoints ---
109
- app.all(`${prefix}/meta*`, async (c) => {
110
- try {
111
- const path = c.req.path.substring(c.req.path.indexOf('/meta') + 5);
112
- const method = c.req.method;
113
- let body = undefined;
114
-
115
- if (method === 'PUT' || method === 'POST') {
116
- // Attempt to parse JSON body
117
- try {
118
- body = await c.req.json();
119
- } catch (e) {
120
- // Ignore parse errors, body remains undefined or empty
121
- body = {};
122
- }
123
- }
124
- const query = c.req.query();
125
-
126
- const result = await dispatcher.handleMetadata(path, { request: c.req.raw }, method, body, query);
127
- return normalizeResponse(c, result);
128
- } catch (err: any) {
129
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
130
- }
131
- });
132
-
133
- // --- 4. Data Endpoints ---
134
- app.all(`${prefix}/data*`, async (c) => {
135
- try {
136
- const path = c.req.path.substring(c.req.path.indexOf('/data') + 5);
137
- const method = c.req.method;
138
-
139
- let body = {};
140
- if (method === 'POST' || method === 'PATCH') {
141
- body = await c.req.json().catch(() => ({}));
142
- }
143
- const query = c.req.query();
144
-
145
- const result = await dispatcher.handleData(path, method, body, query, { request: c.req.raw });
146
- return normalizeResponse(c, result);
147
- } catch (err: any) {
148
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
149
- }
150
- });
151
-
152
- // --- 5. Analytics Endpoints ---
153
- app.all(`${prefix}/analytics*`, async (c) => {
154
- try {
155
- const path = c.req.path.substring(c.req.path.indexOf('/analytics') + 10);
156
- const method = c.req.method;
157
-
158
- let body = {};
159
- if (method === 'POST') {
160
- body = await c.req.json().catch(() => ({}));
161
- }
162
-
163
- const result = await dispatcher.handleAnalytics(path, method, body, { request: c.req.raw });
164
- return normalizeResponse(c, result);
165
- } catch (err: any) {
166
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
167
- }
168
- });
169
-
170
- // --- 7. Automation Endpoints ---
171
- app.all(`${prefix}/automation*`, async (c) => {
172
- try {
173
- const path = c.req.path.substring(c.req.path.indexOf('/automation') + 11);
174
- const method = c.req.method;
175
-
176
- let body = {};
177
- if (method === 'POST') {
178
- body = await c.req.json().catch(() => ({}));
179
- }
180
-
181
- const result = await dispatcher.handleAutomation(path, method, body, { request: c.req.raw });
182
- return normalizeResponse(c, result);
183
- } catch (err: any) {
184
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
185
- }
186
- });
187
-
188
- // --- 8. Storage Endpoints ---
189
- app.all(`${prefix}/storage*`, async (c) => {
190
- try {
191
- const path = c.req.path.substring(c.req.path.indexOf('/storage') + 8);
192
- const method = c.req.method;
193
-
194
- let file: any = undefined;
195
- if (method === 'POST' && path.includes('upload')) {
196
- const body = await c.req.parseBody();
197
- file = body['file'];
198
- }
199
-
200
- const result = await dispatcher.handleStorage(path, method, file, { request: c.req.raw });
201
- return normalizeResponse(c, result);
202
- } catch (err: any) {
203
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
204
- }
205
- });
206
-
207
- // --- 9. Package Management Endpoints ---
208
- app.all(`${prefix}/packages*`, async (c) => {
209
- try {
210
- const packagesIndex = c.req.path.indexOf('/packages');
211
- const path = c.req.path.substring(packagesIndex + 9); // length of '/packages'
212
- const method = c.req.method;
213
-
214
- let body = {};
215
- if (method === 'POST' || method === 'PATCH' || method === 'PUT') {
216
- body = await c.req.json().catch(() => ({}));
217
- }
218
- const query = c.req.query();
219
-
220
- const result = await dispatcher.handlePackages(path, method, body, query, { request: c.req.raw });
221
- return normalizeResponse(c, result);
222
- } catch (err: any) {
223
- return c.json({ success: false, error: { message: err.message, code: err.statusCode || 500 } }, err.statusCode || 500);
224
- }
225
- });
226
-
227
- return app;
228
- }
229
-
230
10
  /**
231
11
  * Middleware mode for existing Hono apps
232
12
  */