@objectstack/hono 3.0.2 → 3.0.4

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@3.0.2 build /home/runner/work/spec/spec/packages/adapters/hono
2
+ > @objectstack/hono@3.0.4 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 216.00 B
14
- ESM dist/index.mjs.map 648.00 B
15
- ESM ⚡️ Build success in 46ms
16
- CJS dist/index.js 1.22 KB
17
- CJS dist/index.js.map 692.00 B
18
- CJS ⚡️ Build success in 49ms
13
+ ESM dist/index.mjs 5.08 KB
14
+ ESM dist/index.mjs.map 10.54 KB
15
+ ESM ⚡️ Build success in 38ms
16
+ CJS dist/index.js 6.15 KB
17
+ CJS dist/index.js.map 10.58 KB
18
+ CJS ⚡️ Build success in 40ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 5986ms
21
- DTS dist/index.d.mts 352.00 B
22
- DTS dist/index.d.ts 352.00 B
20
+ DTS ⚡️ Build success in 11628ms
21
+ DTS dist/index.d.mts 812.00 B
22
+ DTS dist/index.d.ts 812.00 B
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @objectstack/hono
2
2
 
3
+ ## 3.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - @objectstack/runtime@3.0.4
8
+
9
+ ## 3.0.3
10
+
11
+ ### Patch Changes
12
+
13
+ - c7267f6: Patch release for maintenance updates and improvements.
14
+ - Updated dependencies [c7267f6]
15
+ - @objectstack/runtime@3.0.3
16
+
3
17
  ## 3.0.2
4
18
 
5
19
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Hono } from 'hono';
1
2
  import { ObjectKernel } from '@objectstack/runtime';
2
3
 
3
4
  interface ObjectStackHonoOptions {
@@ -8,5 +9,18 @@ interface ObjectStackHonoOptions {
8
9
  * Middleware mode for existing Hono apps
9
10
  */
10
11
  declare function objectStackMiddleware(kernel: ObjectKernel): (c: any, next: any) => Promise<void>;
12
+ /**
13
+ * Creates a full-featured Hono app with all ObjectStack route dispatchers.
14
+ * Provides Auth, GraphQL, Metadata, Data, and Storage routes matching
15
+ * Next.js/NestJS adapter completeness.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { createHonoApp } from '@objectstack/hono';
20
+ * const app = createHonoApp({ kernel });
21
+ * export default app;
22
+ * ```
23
+ */
24
+ declare function createHonoApp(options: ObjectStackHonoOptions): Hono;
11
25
 
12
- export { type ObjectStackHonoOptions, objectStackMiddleware };
26
+ export { type ObjectStackHonoOptions, createHonoApp, objectStackMiddleware };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Hono } from 'hono';
1
2
  import { ObjectKernel } from '@objectstack/runtime';
2
3
 
3
4
  interface ObjectStackHonoOptions {
@@ -8,5 +9,18 @@ interface ObjectStackHonoOptions {
8
9
  * Middleware mode for existing Hono apps
9
10
  */
10
11
  declare function objectStackMiddleware(kernel: ObjectKernel): (c: any, next: any) => Promise<void>;
12
+ /**
13
+ * Creates a full-featured Hono app with all ObjectStack route dispatchers.
14
+ * Provides Auth, GraphQL, Metadata, Data, and Storage routes matching
15
+ * Next.js/NestJS adapter completeness.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { createHonoApp } from '@objectstack/hono';
20
+ * const app = createHonoApp({ kernel });
21
+ * export default app;
22
+ * ```
23
+ */
24
+ declare function createHonoApp(options: ObjectStackHonoOptions): Hono;
11
25
 
12
- export { type ObjectStackHonoOptions, objectStackMiddleware };
26
+ export { type ObjectStackHonoOptions, createHonoApp, objectStackMiddleware };
package/dist/index.js CHANGED
@@ -20,17 +20,149 @@ 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,
23
24
  objectStackMiddleware: () => objectStackMiddleware
24
25
  });
25
26
  module.exports = __toCommonJS(index_exports);
27
+ var import_hono = require("hono");
28
+ var import_runtime = require("@objectstack/runtime");
26
29
  function objectStackMiddleware(kernel) {
27
30
  return async (c, next) => {
28
31
  c.set("objectStack", kernel);
29
32
  await next();
30
33
  };
31
34
  }
35
+ function createHonoApp(options) {
36
+ const app = new import_hono.Hono();
37
+ const prefix = options.prefix || "/api";
38
+ const dispatcher = new import_runtime.HttpDispatcher(options.kernel);
39
+ const errorJson = (c, message, code = 500) => {
40
+ return c.json({ success: false, error: { message, code } }, code);
41
+ };
42
+ const toResponse = (c, result) => {
43
+ if (result.handled) {
44
+ if (result.response) {
45
+ if (result.response.headers) {
46
+ Object.entries(result.response.headers).forEach(([k, v]) => c.header(k, v));
47
+ }
48
+ return c.json(result.response.body, result.response.status);
49
+ }
50
+ if (result.result) {
51
+ const res = result.result;
52
+ if (res.type === "redirect" && res.url) {
53
+ return c.redirect(res.url);
54
+ }
55
+ if (res.type === "stream" && res.stream) {
56
+ if (res.headers) {
57
+ Object.entries(res.headers).forEach(([k, v]) => c.header(k, v));
58
+ }
59
+ return new Response(res.stream, { status: 200 });
60
+ }
61
+ return c.json(res, 200);
62
+ }
63
+ }
64
+ return errorJson(c, "Not Found", 404);
65
+ };
66
+ app.get(`${prefix}`, (c) => {
67
+ return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });
68
+ });
69
+ app.get("/.well-known/objectstack", (c) => {
70
+ return c.redirect(prefix);
71
+ });
72
+ app.all(`${prefix}/auth/*`, async (c) => {
73
+ try {
74
+ const path = c.req.path.substring(`${prefix}/auth/`.length);
75
+ const method = c.req.method;
76
+ const authService = typeof options.kernel.getService === "function" ? options.kernel.getService("auth") : null;
77
+ if (authService && typeof authService.handleRequest === "function") {
78
+ const response = await authService.handleRequest(c.req.raw);
79
+ return new Response(response.body, {
80
+ status: response.status,
81
+ headers: response.headers
82
+ });
83
+ }
84
+ const body = method === "GET" || method === "HEAD" ? {} : await c.req.json().catch(() => ({}));
85
+ const result = await dispatcher.handleAuth(path, method, body, { request: c.req.raw });
86
+ return toResponse(c, result);
87
+ } catch (err) {
88
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
89
+ }
90
+ });
91
+ app.post(`${prefix}/graphql`, async (c) => {
92
+ try {
93
+ const body = await c.req.json();
94
+ const result = await dispatcher.handleGraphQL(body, { request: c.req.raw });
95
+ return c.json(result);
96
+ } catch (err) {
97
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
98
+ }
99
+ });
100
+ app.all(`${prefix}/meta/*`, async (c) => {
101
+ try {
102
+ const subPath = c.req.path.substring(`${prefix}/meta`.length);
103
+ const method = c.req.method;
104
+ let body = void 0;
105
+ if (method === "PUT" || method === "POST") {
106
+ body = await c.req.json().catch(() => ({}));
107
+ }
108
+ const result = await dispatcher.handleMetadata(subPath, { request: c.req.raw }, method, body);
109
+ return toResponse(c, result);
110
+ } catch (err) {
111
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
112
+ }
113
+ });
114
+ app.all(`${prefix}/meta`, async (c) => {
115
+ try {
116
+ const method = c.req.method;
117
+ let body = void 0;
118
+ if (method === "PUT" || method === "POST") {
119
+ body = await c.req.json().catch(() => ({}));
120
+ }
121
+ const result = await dispatcher.handleMetadata("", { request: c.req.raw }, method, body);
122
+ return toResponse(c, result);
123
+ } catch (err) {
124
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
125
+ }
126
+ });
127
+ app.all(`${prefix}/data/*`, async (c) => {
128
+ try {
129
+ const subPath = c.req.path.substring(`${prefix}/data`.length);
130
+ const method = c.req.method;
131
+ let body = {};
132
+ if (method === "POST" || method === "PATCH") {
133
+ body = await c.req.json().catch(() => ({}));
134
+ }
135
+ const queryParams = {};
136
+ const url = new URL(c.req.url);
137
+ url.searchParams.forEach((val, key) => {
138
+ queryParams[key] = val;
139
+ });
140
+ const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: c.req.raw });
141
+ return toResponse(c, result);
142
+ } catch (err) {
143
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
144
+ }
145
+ });
146
+ app.all(`${prefix}/storage/*`, async (c) => {
147
+ try {
148
+ const subPath = c.req.path.substring(`${prefix}/storage`.length);
149
+ const method = c.req.method;
150
+ let file = void 0;
151
+ if (method === "POST" && subPath === "/upload") {
152
+ const formData = await c.req.formData();
153
+ file = formData.get("file");
154
+ }
155
+ const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });
156
+ return toResponse(c, result);
157
+ } catch (err) {
158
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
159
+ }
160
+ });
161
+ return app;
162
+ }
32
163
  // Annotate the CommonJS export names for ESM import in node:
33
164
  0 && (module.exports = {
165
+ createHonoApp,
34
166
  objectStackMiddleware
35
167
  });
36
168
  //# 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 { 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":[]}
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 { 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 * 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\n/**\n * Creates a full-featured Hono app with all ObjectStack route dispatchers.\n * Provides Auth, GraphQL, Metadata, Data, and Storage routes matching\n * Next.js/NestJS adapter completeness.\n *\n * @example\n * ```ts\n * import { createHonoApp } from '@objectstack/hono';\n * const app = createHonoApp({ kernel });\n * export default app;\n * ```\n */\nexport function createHonoApp(options: ObjectStackHonoOptions): Hono {\n const app = new Hono();\n const prefix = options.prefix || '/api';\n const dispatcher = new HttpDispatcher(options.kernel);\n\n const errorJson = (c: any, message: string, code: number = 500) => {\n return c.json({ success: false, error: { message, code } }, code);\n };\n\n const toResponse = (c: any, result: HttpDispatcherResult) => {\n if (result.handled) {\n if (result.response) {\n if (result.response.headers) {\n Object.entries(result.response.headers).forEach(([k, v]) => c.header(k, v as string));\n }\n return c.json(result.response.body, result.response.status);\n }\n if (result.result) {\n const res = result.result;\n if (res.type === 'redirect' && res.url) {\n return c.redirect(res.url);\n }\n if (res.type === 'stream' && res.stream) {\n if (res.headers) {\n Object.entries(res.headers).forEach(([k, v]) => c.header(k, v as string));\n }\n return new Response(res.stream, { status: 200 });\n }\n return c.json(res, 200);\n }\n }\n return errorJson(c, 'Not Found', 404);\n };\n\n // --- Discovery ---\n app.get(`${prefix}`, (c) => {\n return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });\n });\n\n // --- .well-known ---\n app.get('/.well-known/objectstack', (c) => {\n return c.redirect(prefix);\n });\n\n // --- Auth ---\n app.all(`${prefix}/auth/*`, async (c) => {\n try {\n const path = c.req.path.substring(`${prefix}/auth/`.length);\n const method = c.req.method;\n\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 new Response(response.body, {\n status: response.status,\n headers: response.headers,\n });\n }\n\n // Fallback to legacy dispatcher\n const body = method === 'GET' || method === 'HEAD'\n ? {}\n : await c.req.json().catch(() => ({}));\n const result = await dispatcher.handleAuth(path, method, body, { request: c.req.raw });\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // --- 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 errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // --- Metadata ---\n app.all(`${prefix}/meta/*`, async (c) => {\n try {\n const subPath = c.req.path.substring(`${prefix}/meta`.length);\n const method = c.req.method;\n\n let body: any = undefined;\n if (method === 'PUT' || method === 'POST') {\n body = await c.req.json().catch(() => ({}));\n }\n\n const result = await dispatcher.handleMetadata(subPath, { request: c.req.raw }, method, body);\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // Also handle /meta with no trailing path\n app.all(`${prefix}/meta`, async (c) => {\n try {\n const method = c.req.method;\n let body: any = undefined;\n if (method === 'PUT' || method === 'POST') {\n body = await c.req.json().catch(() => ({}));\n }\n const result = await dispatcher.handleMetadata('', { request: c.req.raw }, method, body);\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // --- Data ---\n app.all(`${prefix}/data/*`, async (c) => {\n try {\n const subPath = c.req.path.substring(`${prefix}/data`.length);\n const method = c.req.method;\n\n let body: any = {};\n if (method === 'POST' || method === 'PATCH') {\n body = await c.req.json().catch(() => ({}));\n }\n\n const queryParams: Record<string, any> = {};\n const url = new URL(c.req.url);\n url.searchParams.forEach((val, key) => { queryParams[key] = val; });\n\n const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: c.req.raw });\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // --- Storage ---\n app.all(`${prefix}/storage/*`, async (c) => {\n try {\n const subPath = c.req.path.substring(`${prefix}/storage`.length);\n const method = c.req.method;\n\n let file: any = undefined;\n if (method === 'POST' && subPath === '/upload') {\n const formData = await c.req.formData();\n file = formData.get('file');\n }\n\n const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n return app;\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -1,11 +1,142 @@
1
1
  // src/index.ts
2
+ import { Hono } from "hono";
3
+ import { HttpDispatcher } from "@objectstack/runtime";
2
4
  function objectStackMiddleware(kernel) {
3
5
  return async (c, next) => {
4
6
  c.set("objectStack", kernel);
5
7
  await next();
6
8
  };
7
9
  }
10
+ function createHonoApp(options) {
11
+ const app = new Hono();
12
+ const prefix = options.prefix || "/api";
13
+ const dispatcher = new HttpDispatcher(options.kernel);
14
+ const errorJson = (c, message, code = 500) => {
15
+ return c.json({ success: false, error: { message, code } }, code);
16
+ };
17
+ const toResponse = (c, result) => {
18
+ if (result.handled) {
19
+ if (result.response) {
20
+ if (result.response.headers) {
21
+ Object.entries(result.response.headers).forEach(([k, v]) => c.header(k, v));
22
+ }
23
+ return c.json(result.response.body, result.response.status);
24
+ }
25
+ if (result.result) {
26
+ const res = result.result;
27
+ if (res.type === "redirect" && res.url) {
28
+ return c.redirect(res.url);
29
+ }
30
+ if (res.type === "stream" && res.stream) {
31
+ if (res.headers) {
32
+ Object.entries(res.headers).forEach(([k, v]) => c.header(k, v));
33
+ }
34
+ return new Response(res.stream, { status: 200 });
35
+ }
36
+ return c.json(res, 200);
37
+ }
38
+ }
39
+ return errorJson(c, "Not Found", 404);
40
+ };
41
+ app.get(`${prefix}`, (c) => {
42
+ return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });
43
+ });
44
+ app.get("/.well-known/objectstack", (c) => {
45
+ return c.redirect(prefix);
46
+ });
47
+ app.all(`${prefix}/auth/*`, async (c) => {
48
+ try {
49
+ const path = c.req.path.substring(`${prefix}/auth/`.length);
50
+ const method = c.req.method;
51
+ const authService = typeof options.kernel.getService === "function" ? options.kernel.getService("auth") : null;
52
+ if (authService && typeof authService.handleRequest === "function") {
53
+ const response = await authService.handleRequest(c.req.raw);
54
+ return new Response(response.body, {
55
+ status: response.status,
56
+ headers: response.headers
57
+ });
58
+ }
59
+ const body = method === "GET" || method === "HEAD" ? {} : await c.req.json().catch(() => ({}));
60
+ const result = await dispatcher.handleAuth(path, method, body, { request: c.req.raw });
61
+ return toResponse(c, result);
62
+ } catch (err) {
63
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
64
+ }
65
+ });
66
+ app.post(`${prefix}/graphql`, async (c) => {
67
+ try {
68
+ const body = await c.req.json();
69
+ const result = await dispatcher.handleGraphQL(body, { request: c.req.raw });
70
+ return c.json(result);
71
+ } catch (err) {
72
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
73
+ }
74
+ });
75
+ app.all(`${prefix}/meta/*`, async (c) => {
76
+ try {
77
+ const subPath = c.req.path.substring(`${prefix}/meta`.length);
78
+ const method = c.req.method;
79
+ let body = void 0;
80
+ if (method === "PUT" || method === "POST") {
81
+ body = await c.req.json().catch(() => ({}));
82
+ }
83
+ const result = await dispatcher.handleMetadata(subPath, { request: c.req.raw }, method, body);
84
+ return toResponse(c, result);
85
+ } catch (err) {
86
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
87
+ }
88
+ });
89
+ app.all(`${prefix}/meta`, async (c) => {
90
+ try {
91
+ const method = c.req.method;
92
+ let body = void 0;
93
+ if (method === "PUT" || method === "POST") {
94
+ body = await c.req.json().catch(() => ({}));
95
+ }
96
+ const result = await dispatcher.handleMetadata("", { request: c.req.raw }, method, body);
97
+ return toResponse(c, result);
98
+ } catch (err) {
99
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
100
+ }
101
+ });
102
+ app.all(`${prefix}/data/*`, async (c) => {
103
+ try {
104
+ const subPath = c.req.path.substring(`${prefix}/data`.length);
105
+ const method = c.req.method;
106
+ let body = {};
107
+ if (method === "POST" || method === "PATCH") {
108
+ body = await c.req.json().catch(() => ({}));
109
+ }
110
+ const queryParams = {};
111
+ const url = new URL(c.req.url);
112
+ url.searchParams.forEach((val, key) => {
113
+ queryParams[key] = val;
114
+ });
115
+ const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: c.req.raw });
116
+ return toResponse(c, result);
117
+ } catch (err) {
118
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
119
+ }
120
+ });
121
+ app.all(`${prefix}/storage/*`, async (c) => {
122
+ try {
123
+ const subPath = c.req.path.substring(`${prefix}/storage`.length);
124
+ const method = c.req.method;
125
+ let file = void 0;
126
+ if (method === "POST" && subPath === "/upload") {
127
+ const formData = await c.req.formData();
128
+ file = formData.get("file");
129
+ }
130
+ const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });
131
+ return toResponse(c, result);
132
+ } catch (err) {
133
+ return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
134
+ }
135
+ });
136
+ return app;
137
+ }
8
138
  export {
139
+ createHonoApp,
9
140
  objectStackMiddleware
10
141
  };
11
142
  //# 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 { 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":[]}
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 { 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 * 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\n/**\n * Creates a full-featured Hono app with all ObjectStack route dispatchers.\n * Provides Auth, GraphQL, Metadata, Data, and Storage routes matching\n * Next.js/NestJS adapter completeness.\n *\n * @example\n * ```ts\n * import { createHonoApp } from '@objectstack/hono';\n * const app = createHonoApp({ kernel });\n * export default app;\n * ```\n */\nexport function createHonoApp(options: ObjectStackHonoOptions): Hono {\n const app = new Hono();\n const prefix = options.prefix || '/api';\n const dispatcher = new HttpDispatcher(options.kernel);\n\n const errorJson = (c: any, message: string, code: number = 500) => {\n return c.json({ success: false, error: { message, code } }, code);\n };\n\n const toResponse = (c: any, result: HttpDispatcherResult) => {\n if (result.handled) {\n if (result.response) {\n if (result.response.headers) {\n Object.entries(result.response.headers).forEach(([k, v]) => c.header(k, v as string));\n }\n return c.json(result.response.body, result.response.status);\n }\n if (result.result) {\n const res = result.result;\n if (res.type === 'redirect' && res.url) {\n return c.redirect(res.url);\n }\n if (res.type === 'stream' && res.stream) {\n if (res.headers) {\n Object.entries(res.headers).forEach(([k, v]) => c.header(k, v as string));\n }\n return new Response(res.stream, { status: 200 });\n }\n return c.json(res, 200);\n }\n }\n return errorJson(c, 'Not Found', 404);\n };\n\n // --- Discovery ---\n app.get(`${prefix}`, (c) => {\n return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });\n });\n\n // --- .well-known ---\n app.get('/.well-known/objectstack', (c) => {\n return c.redirect(prefix);\n });\n\n // --- Auth ---\n app.all(`${prefix}/auth/*`, async (c) => {\n try {\n const path = c.req.path.substring(`${prefix}/auth/`.length);\n const method = c.req.method;\n\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 new Response(response.body, {\n status: response.status,\n headers: response.headers,\n });\n }\n\n // Fallback to legacy dispatcher\n const body = method === 'GET' || method === 'HEAD'\n ? {}\n : await c.req.json().catch(() => ({}));\n const result = await dispatcher.handleAuth(path, method, body, { request: c.req.raw });\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // --- 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 errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // --- Metadata ---\n app.all(`${prefix}/meta/*`, async (c) => {\n try {\n const subPath = c.req.path.substring(`${prefix}/meta`.length);\n const method = c.req.method;\n\n let body: any = undefined;\n if (method === 'PUT' || method === 'POST') {\n body = await c.req.json().catch(() => ({}));\n }\n\n const result = await dispatcher.handleMetadata(subPath, { request: c.req.raw }, method, body);\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // Also handle /meta with no trailing path\n app.all(`${prefix}/meta`, async (c) => {\n try {\n const method = c.req.method;\n let body: any = undefined;\n if (method === 'PUT' || method === 'POST') {\n body = await c.req.json().catch(() => ({}));\n }\n const result = await dispatcher.handleMetadata('', { request: c.req.raw }, method, body);\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // --- Data ---\n app.all(`${prefix}/data/*`, async (c) => {\n try {\n const subPath = c.req.path.substring(`${prefix}/data`.length);\n const method = c.req.method;\n\n let body: any = {};\n if (method === 'POST' || method === 'PATCH') {\n body = await c.req.json().catch(() => ({}));\n }\n\n const queryParams: Record<string, any> = {};\n const url = new URL(c.req.url);\n url.searchParams.forEach((val, key) => { queryParams[key] = val; });\n\n const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: c.req.raw });\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n // --- Storage ---\n app.all(`${prefix}/storage/*`, async (c) => {\n try {\n const subPath = c.req.path.substring(`${prefix}/storage`.length);\n const method = c.req.method;\n\n let file: any = undefined;\n if (method === 'POST' && subPath === '/upload') {\n const formData = await c.req.formData();\n file = formData.get('file');\n }\n\n const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });\n return toResponse(c, result);\n } catch (err: any) {\n return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);\n }\n });\n\n return app;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/hono",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
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": "3.0.2"
16
+ "@objectstack/runtime": "3.0.4"
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": "3.0.2"
22
+ "@objectstack/runtime": "3.0.4"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup --config ../../../tsup.config.ts",
@@ -1,4 +1,16 @@
1
- // Stub for @objectstack/runtime - replaced by vi.mock in tests
2
- export const HttpDispatcher = class {};
1
+ // Stub for @objectstack/runtime - resolved via vitest alias
2
+ import { vi } from 'vitest';
3
+
4
+ export class HttpDispatcher {
5
+ getDiscoveryInfo = vi.fn().mockReturnValue({ version: '1.0', endpoints: [] });
6
+ handleGraphQL = vi.fn().mockResolvedValue({ data: {} });
7
+ handleAuth = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { ok: true } } });
8
+ handleMetadata = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { objects: [] } } });
9
+ handleData = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { records: [] } } });
10
+ handleStorage = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: {} } });
11
+
12
+ constructor(_kernel: any) {}
13
+ }
14
+
3
15
  export type ObjectKernel = any;
4
16
  export type HttpDispatcherResult = any;
package/src/hono.test.ts CHANGED
@@ -3,7 +3,25 @@
3
3
  import { describe, it, expect, vi, beforeEach } from 'vitest';
4
4
  import { Hono } from 'hono';
5
5
 
6
- import { objectStackMiddleware } from './index';
6
+ // Mock dispatcher instance accessible across tests
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
+ handleStorage: vi.fn().mockResolvedValue({ handled: true, response: { body: {}, status: 200 } }),
14
+ };
15
+
16
+ vi.mock('@objectstack/runtime', () => {
17
+ return {
18
+ HttpDispatcher: function HttpDispatcher() {
19
+ return mockDispatcher;
20
+ },
21
+ };
22
+ });
23
+
24
+ import { objectStackMiddleware, createHonoApp } from './index';
7
25
 
8
26
  const mockKernel = { name: 'test-kernel' } as any;
9
27
 
@@ -61,3 +79,327 @@ describe('objectStackMiddleware', () => {
61
79
  expect(json.name).toBe('test-kernel');
62
80
  });
63
81
  });
82
+
83
+ describe('createHonoApp', () => {
84
+ let app: Hono;
85
+
86
+ beforeEach(() => {
87
+ vi.clearAllMocks();
88
+ app = createHonoApp({ kernel: mockKernel });
89
+ });
90
+
91
+ describe('Discovery Endpoint', () => {
92
+ it('GET /api returns discovery info', async () => {
93
+ const res = await app.request('/api');
94
+ expect(res.status).toBe(200);
95
+ const json = await res.json();
96
+ expect(json.data).toBeDefined();
97
+ expect(json.data.version).toBe('1.0');
98
+ expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api');
99
+ });
100
+
101
+ it('uses custom prefix for discovery', async () => {
102
+ const customApp = createHonoApp({ kernel: mockKernel, prefix: '/v2' });
103
+ const res = await customApp.request('/v2');
104
+ expect(res.status).toBe(200);
105
+ const json = await res.json();
106
+ expect(json.data).toBeDefined();
107
+ expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/v2');
108
+ });
109
+ });
110
+
111
+ describe('.well-known Endpoint', () => {
112
+ it('GET /.well-known/objectstack redirects to prefix', async () => {
113
+ const res = await app.request('/.well-known/objectstack', { redirect: 'manual' });
114
+ expect(res.status).toBe(302);
115
+ expect(res.headers.get('location')).toBe('/api');
116
+ });
117
+ });
118
+
119
+ describe('Auth Endpoint', () => {
120
+ it('POST /api/auth/login calls handleAuth', async () => {
121
+ const res = await app.request('/api/auth/login', {
122
+ method: 'POST',
123
+ headers: { 'Content-Type': 'application/json' },
124
+ body: JSON.stringify({ email: 'a@b.com' }),
125
+ });
126
+ expect(res.status).toBe(200);
127
+ const json = await res.json();
128
+ expect(json.ok).toBe(true);
129
+ expect(mockDispatcher.handleAuth).toHaveBeenCalledWith(
130
+ 'login',
131
+ 'POST',
132
+ { email: 'a@b.com' },
133
+ expect.objectContaining({ request: expect.anything() }),
134
+ );
135
+ });
136
+
137
+ it('GET /api/auth/callback calls handleAuth with empty body', async () => {
138
+ const res = await app.request('/api/auth/callback');
139
+ expect(res.status).toBe(200);
140
+ expect(mockDispatcher.handleAuth).toHaveBeenCalledWith(
141
+ 'callback',
142
+ 'GET',
143
+ {},
144
+ expect.objectContaining({ request: expect.anything() }),
145
+ );
146
+ });
147
+
148
+ it('returns error on handleAuth exception', async () => {
149
+ mockDispatcher.handleAuth.mockRejectedValueOnce(
150
+ Object.assign(new Error('Unauthorized'), { statusCode: 401 }),
151
+ );
152
+ const res = await app.request('/api/auth/login', {
153
+ method: 'POST',
154
+ headers: { 'Content-Type': 'application/json' },
155
+ body: JSON.stringify({}),
156
+ });
157
+ expect(res.status).toBe(401);
158
+ const json = await res.json();
159
+ expect(json.success).toBe(false);
160
+ expect(json.error.message).toBe('Unauthorized');
161
+ });
162
+ });
163
+
164
+ describe('Auth via AuthPlugin service', () => {
165
+ it('uses kernel.getService("auth") when available', async () => {
166
+ const mockHandleRequest = vi.fn().mockResolvedValue(
167
+ new Response(JSON.stringify({ user: { id: '1' } }), {
168
+ status: 200,
169
+ headers: { 'Content-Type': 'application/json' },
170
+ }),
171
+ );
172
+ const kernelWithAuth = {
173
+ ...mockKernel,
174
+ getService: vi.fn().mockReturnValue({ handleRequest: mockHandleRequest }),
175
+ };
176
+ const authApp = createHonoApp({ kernel: kernelWithAuth });
177
+ const res = await authApp.request('/api/auth/sign-in/email', {
178
+ method: 'POST',
179
+ headers: { 'Content-Type': 'application/json' },
180
+ body: JSON.stringify({ email: 'a@b.com', password: 'pass' }),
181
+ });
182
+ expect(res.status).toBe(200);
183
+ expect(kernelWithAuth.getService).toHaveBeenCalledWith('auth');
184
+ expect(mockHandleRequest).toHaveBeenCalled();
185
+ });
186
+
187
+ it('falls back to dispatcher when auth service is not available', async () => {
188
+ const kernelWithoutAuth = {
189
+ ...mockKernel,
190
+ getService: vi.fn().mockReturnValue(null),
191
+ };
192
+ const authApp = createHonoApp({ kernel: kernelWithoutAuth });
193
+ const res = await authApp.request('/api/auth/login', {
194
+ method: 'POST',
195
+ headers: { 'Content-Type': 'application/json' },
196
+ body: JSON.stringify({ email: 'a@b.com' }),
197
+ });
198
+ expect(res.status).toBe(200);
199
+ expect(mockDispatcher.handleAuth).toHaveBeenCalled();
200
+ });
201
+ });
202
+
203
+ describe('GraphQL Endpoint', () => {
204
+ it('POST /api/graphql calls handleGraphQL', async () => {
205
+ const body = { query: '{ objects { name } }' };
206
+ const res = await app.request('/api/graphql', {
207
+ method: 'POST',
208
+ headers: { 'Content-Type': 'application/json' },
209
+ body: JSON.stringify(body),
210
+ });
211
+ expect(res.status).toBe(200);
212
+ const json = await res.json();
213
+ expect(json.data).toBeDefined();
214
+ expect(mockDispatcher.handleGraphQL).toHaveBeenCalledWith(
215
+ body,
216
+ expect.objectContaining({ request: expect.anything() }),
217
+ );
218
+ });
219
+
220
+ it('returns error on handleGraphQL exception', async () => {
221
+ mockDispatcher.handleGraphQL.mockRejectedValueOnce(new Error('Parse error'));
222
+ const res = await app.request('/api/graphql', {
223
+ method: 'POST',
224
+ headers: { 'Content-Type': 'application/json' },
225
+ body: JSON.stringify({ query: 'bad' }),
226
+ });
227
+ expect(res.status).toBe(500);
228
+ const json = await res.json();
229
+ expect(json.success).toBe(false);
230
+ expect(json.error.message).toBe('Parse error');
231
+ });
232
+ });
233
+
234
+ describe('Metadata Endpoint', () => {
235
+ it('GET /api/meta/objects calls handleMetadata', async () => {
236
+ const res = await app.request('/api/meta/objects');
237
+ expect(res.status).toBe(200);
238
+ const json = await res.json();
239
+ expect(json.objects).toBeDefined();
240
+ expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
241
+ '/objects',
242
+ expect.objectContaining({ request: expect.anything() }),
243
+ 'GET',
244
+ undefined,
245
+ );
246
+ });
247
+
248
+ it('PUT /api/meta/objects parses JSON body', async () => {
249
+ const body = { name: 'test_object' };
250
+ const res = await app.request('/api/meta/objects', {
251
+ method: 'PUT',
252
+ headers: { 'Content-Type': 'application/json' },
253
+ body: JSON.stringify(body),
254
+ });
255
+ expect(res.status).toBe(200);
256
+ expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
257
+ '/objects',
258
+ expect.objectContaining({ request: expect.anything() }),
259
+ 'PUT',
260
+ body,
261
+ );
262
+ });
263
+
264
+ it('GET /api/meta with no trailing path', async () => {
265
+ const res = await app.request('/api/meta');
266
+ expect(res.status).toBe(200);
267
+ expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
268
+ '',
269
+ expect.objectContaining({ request: expect.anything() }),
270
+ 'GET',
271
+ undefined,
272
+ );
273
+ });
274
+ });
275
+
276
+ describe('Data Endpoint', () => {
277
+ it('GET /api/data/account calls handleData', async () => {
278
+ const res = await app.request('/api/data/account');
279
+ expect(res.status).toBe(200);
280
+ const json = await res.json();
281
+ expect(json.records).toBeDefined();
282
+ expect(mockDispatcher.handleData).toHaveBeenCalledWith(
283
+ '/account',
284
+ 'GET',
285
+ {},
286
+ expect.any(Object),
287
+ expect.objectContaining({ request: expect.anything() }),
288
+ );
289
+ });
290
+
291
+ it('POST /api/data/account parses JSON body', async () => {
292
+ const body = { name: 'Acme' };
293
+ const res = await app.request('/api/data/account', {
294
+ method: 'POST',
295
+ headers: { 'Content-Type': 'application/json' },
296
+ body: JSON.stringify(body),
297
+ });
298
+ expect(res.status).toBe(200);
299
+ expect(mockDispatcher.handleData).toHaveBeenCalledWith(
300
+ '/account',
301
+ 'POST',
302
+ body,
303
+ expect.any(Object),
304
+ expect.objectContaining({ request: expect.anything() }),
305
+ );
306
+ });
307
+
308
+ it('PATCH /api/data/account parses JSON body', async () => {
309
+ const body = { name: 'Updated' };
310
+ const res = await app.request('/api/data/account', {
311
+ method: 'PATCH',
312
+ headers: { 'Content-Type': 'application/json' },
313
+ body: JSON.stringify(body),
314
+ });
315
+ expect(res.status).toBe(200);
316
+ expect(mockDispatcher.handleData).toHaveBeenCalledWith(
317
+ '/account',
318
+ 'PATCH',
319
+ body,
320
+ expect.any(Object),
321
+ expect.objectContaining({ request: expect.anything() }),
322
+ );
323
+ });
324
+
325
+ it('returns 404 when result is not handled', async () => {
326
+ mockDispatcher.handleData.mockResolvedValueOnce({ handled: false });
327
+ const res = await app.request('/api/data/missing');
328
+ expect(res.status).toBe(404);
329
+ const json = await res.json();
330
+ expect(json.success).toBe(false);
331
+ });
332
+ });
333
+
334
+ describe('Storage Endpoint', () => {
335
+ it('GET /api/storage/files calls handleStorage', async () => {
336
+ const res = await app.request('/api/storage/files');
337
+ expect(res.status).toBe(200);
338
+ expect(mockDispatcher.handleStorage).toHaveBeenCalledWith(
339
+ '/files',
340
+ 'GET',
341
+ undefined,
342
+ expect.objectContaining({ request: expect.anything() }),
343
+ );
344
+ });
345
+ });
346
+
347
+ describe('Error Handling', () => {
348
+ it('returns 500 with default message on generic error', async () => {
349
+ mockDispatcher.handleData.mockRejectedValueOnce(new Error());
350
+ const res = await app.request('/api/data/account');
351
+ expect(res.status).toBe(500);
352
+ const json = await res.json();
353
+ expect(json.error.message).toBe('Internal Server Error');
354
+ });
355
+
356
+ it('uses custom statusCode from error', async () => {
357
+ mockDispatcher.handleData.mockRejectedValueOnce(
358
+ Object.assign(new Error('Forbidden'), { statusCode: 403 }),
359
+ );
360
+ const res = await app.request('/api/data/account');
361
+ expect(res.status).toBe(403);
362
+ const json = await res.json();
363
+ expect(json.error.message).toBe('Forbidden');
364
+ });
365
+ });
366
+
367
+ describe('toResponse', () => {
368
+ it('handles redirect result', async () => {
369
+ mockDispatcher.handleData.mockResolvedValueOnce({
370
+ handled: true,
371
+ result: { type: 'redirect', url: 'https://example.com' },
372
+ });
373
+ const res = await app.request('/api/data/redir', { redirect: 'manual' });
374
+ expect(res.status).toBe(302);
375
+ expect(res.headers.get('location')).toBe('https://example.com');
376
+ });
377
+
378
+ it('handles generic result objects with 200 status', async () => {
379
+ mockDispatcher.handleData.mockResolvedValueOnce({
380
+ handled: true,
381
+ result: { foo: 'bar' },
382
+ });
383
+ const res = await app.request('/api/data/custom');
384
+ expect(res.status).toBe(200);
385
+ const json = await res.json();
386
+ expect(json.foo).toBe('bar');
387
+ });
388
+
389
+ it('sets custom headers from response', async () => {
390
+ mockDispatcher.handleData.mockResolvedValueOnce({
391
+ handled: true,
392
+ response: { status: 201, body: { id: 1 }, headers: { 'X-Custom': 'yes' } },
393
+ });
394
+ const res = await app.request('/api/data/account', {
395
+ method: 'POST',
396
+ headers: { 'Content-Type': 'application/json' },
397
+ body: JSON.stringify({ name: 'test' }),
398
+ });
399
+ expect(res.status).toBe(201);
400
+ expect(res.headers.get('X-Custom')).toBe('yes');
401
+ const json = await res.json();
402
+ expect(json.id).toBe(1);
403
+ });
404
+ });
405
+ });
package/src/index.ts CHANGED
@@ -1,12 +1,20 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
- import { type ObjectKernel } from '@objectstack/runtime';
3
+ import { Hono } from 'hono';
4
+ import { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';
4
5
 
5
6
  export interface ObjectStackHonoOptions {
6
7
  kernel: ObjectKernel;
7
8
  prefix?: string;
8
9
  }
9
10
 
11
+ /**
12
+ * Auth service interface with handleRequest method
13
+ */
14
+ interface AuthService {
15
+ handleRequest(request: Request): Promise<Response>;
16
+ }
17
+
10
18
  /**
11
19
  * Middleware mode for existing Hono apps
12
20
  */
@@ -16,3 +24,177 @@ export function objectStackMiddleware(kernel: ObjectKernel) {
16
24
  await next();
17
25
  };
18
26
  }
27
+
28
+ /**
29
+ * Creates a full-featured Hono app with all ObjectStack route dispatchers.
30
+ * Provides Auth, GraphQL, Metadata, Data, and Storage routes matching
31
+ * Next.js/NestJS adapter completeness.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { createHonoApp } from '@objectstack/hono';
36
+ * const app = createHonoApp({ kernel });
37
+ * export default app;
38
+ * ```
39
+ */
40
+ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
41
+ const app = new Hono();
42
+ const prefix = options.prefix || '/api';
43
+ const dispatcher = new HttpDispatcher(options.kernel);
44
+
45
+ const errorJson = (c: any, message: string, code: number = 500) => {
46
+ return c.json({ success: false, error: { message, code } }, code);
47
+ };
48
+
49
+ const toResponse = (c: any, result: HttpDispatcherResult) => {
50
+ if (result.handled) {
51
+ if (result.response) {
52
+ if (result.response.headers) {
53
+ Object.entries(result.response.headers).forEach(([k, v]) => c.header(k, v as string));
54
+ }
55
+ return c.json(result.response.body, result.response.status);
56
+ }
57
+ if (result.result) {
58
+ const res = result.result;
59
+ if (res.type === 'redirect' && res.url) {
60
+ return c.redirect(res.url);
61
+ }
62
+ if (res.type === 'stream' && res.stream) {
63
+ if (res.headers) {
64
+ Object.entries(res.headers).forEach(([k, v]) => c.header(k, v as string));
65
+ }
66
+ return new Response(res.stream, { status: 200 });
67
+ }
68
+ return c.json(res, 200);
69
+ }
70
+ }
71
+ return errorJson(c, 'Not Found', 404);
72
+ };
73
+
74
+ // --- Discovery ---
75
+ app.get(`${prefix}`, (c) => {
76
+ return c.json({ data: dispatcher.getDiscoveryInfo(prefix) });
77
+ });
78
+
79
+ // --- .well-known ---
80
+ app.get('/.well-known/objectstack', (c) => {
81
+ return c.redirect(prefix);
82
+ });
83
+
84
+ // --- Auth ---
85
+ app.all(`${prefix}/auth/*`, async (c) => {
86
+ try {
87
+ const path = c.req.path.substring(`${prefix}/auth/`.length);
88
+ const method = c.req.method;
89
+
90
+ // Try AuthPlugin service first (preferred path)
91
+ const authService = typeof options.kernel.getService === 'function'
92
+ ? options.kernel.getService<AuthService>('auth')
93
+ : null;
94
+
95
+ if (authService && typeof authService.handleRequest === 'function') {
96
+ const response = await authService.handleRequest(c.req.raw);
97
+ return new Response(response.body, {
98
+ status: response.status,
99
+ headers: response.headers,
100
+ });
101
+ }
102
+
103
+ // Fallback to legacy dispatcher
104
+ const body = method === 'GET' || method === 'HEAD'
105
+ ? {}
106
+ : await c.req.json().catch(() => ({}));
107
+ const result = await dispatcher.handleAuth(path, method, body, { request: c.req.raw });
108
+ return toResponse(c, result);
109
+ } catch (err: any) {
110
+ return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
111
+ }
112
+ });
113
+
114
+ // --- GraphQL ---
115
+ app.post(`${prefix}/graphql`, async (c) => {
116
+ try {
117
+ const body = await c.req.json();
118
+ const result = await dispatcher.handleGraphQL(body, { request: c.req.raw });
119
+ return c.json(result);
120
+ } catch (err: any) {
121
+ return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
122
+ }
123
+ });
124
+
125
+ // --- Metadata ---
126
+ app.all(`${prefix}/meta/*`, async (c) => {
127
+ try {
128
+ const subPath = c.req.path.substring(`${prefix}/meta`.length);
129
+ const method = c.req.method;
130
+
131
+ let body: any = undefined;
132
+ if (method === 'PUT' || method === 'POST') {
133
+ body = await c.req.json().catch(() => ({}));
134
+ }
135
+
136
+ const result = await dispatcher.handleMetadata(subPath, { request: c.req.raw }, method, body);
137
+ return toResponse(c, result);
138
+ } catch (err: any) {
139
+ return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
140
+ }
141
+ });
142
+
143
+ // Also handle /meta with no trailing path
144
+ app.all(`${prefix}/meta`, async (c) => {
145
+ try {
146
+ const method = c.req.method;
147
+ let body: any = undefined;
148
+ if (method === 'PUT' || method === 'POST') {
149
+ body = await c.req.json().catch(() => ({}));
150
+ }
151
+ const result = await dispatcher.handleMetadata('', { request: c.req.raw }, method, body);
152
+ return toResponse(c, result);
153
+ } catch (err: any) {
154
+ return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
155
+ }
156
+ });
157
+
158
+ // --- Data ---
159
+ app.all(`${prefix}/data/*`, async (c) => {
160
+ try {
161
+ const subPath = c.req.path.substring(`${prefix}/data`.length);
162
+ const method = c.req.method;
163
+
164
+ let body: any = {};
165
+ if (method === 'POST' || method === 'PATCH') {
166
+ body = await c.req.json().catch(() => ({}));
167
+ }
168
+
169
+ const queryParams: Record<string, any> = {};
170
+ const url = new URL(c.req.url);
171
+ url.searchParams.forEach((val, key) => { queryParams[key] = val; });
172
+
173
+ const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: c.req.raw });
174
+ return toResponse(c, result);
175
+ } catch (err: any) {
176
+ return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
177
+ }
178
+ });
179
+
180
+ // --- Storage ---
181
+ app.all(`${prefix}/storage/*`, async (c) => {
182
+ try {
183
+ const subPath = c.req.path.substring(`${prefix}/storage`.length);
184
+ const method = c.req.method;
185
+
186
+ let file: any = undefined;
187
+ if (method === 'POST' && subPath === '/upload') {
188
+ const formData = await c.req.formData();
189
+ file = formData.get('file');
190
+ }
191
+
192
+ const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });
193
+ return toResponse(c, result);
194
+ } catch (err: any) {
195
+ return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
196
+ }
197
+ });
198
+
199
+ return app;
200
+ }