@objectstack/hono 3.2.7 → 3.2.8

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.2.7 build /home/runner/work/spec/spec/packages/adapters/hono
2
+ > @objectstack/hono@3.2.8 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 5.33 KB
14
- ESM dist/index.mjs.map 11.07 KB
15
- ESM ⚡️ Build success in 67ms
16
- CJS dist/index.js 6.41 KB
17
- CJS dist/index.js.map 11.11 KB
18
- CJS ⚡️ Build success in 71ms
13
+ CJS dist/index.js 5.41 KB
14
+ CJS dist/index.js.map 9.94 KB
15
+ CJS ⚡️ Build success in 78ms
16
+ ESM dist/index.mjs 4.33 KB
17
+ ESM dist/index.mjs.map 9.90 KB
18
+ ESM ⚡️ Build success in 79ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 13115ms
21
- DTS dist/index.d.mts 812.00 B
22
- DTS dist/index.d.ts 812.00 B
20
+ DTS ⚡️ Build success in 13452ms
21
+ DTS dist/index.d.mts 1.17 KB
22
+ DTS dist/index.d.ts 1.17 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @objectstack/hono
2
2
 
3
+ ## 3.2.8
4
+
5
+ ### Patch Changes
6
+
7
+ - @objectstack/runtime@3.2.8
8
+
9
+ ## 3.2.8
10
+
11
+ ### Patch Changes
12
+
13
+ - fix: unified catch-all dispatch pattern — `createHonoApp()` now delegates all non-framework-specific routes to `HttpDispatcher.dispatch()`, automatically supporting packages, analytics, automation, i18n, ui, openapi, custom endpoints, and any future routes
14
+ - fix: resolves 404 errors for `/api/v1/meta` and `/api/v1/packages` after Vercel deployment
15
+ - Only auth (service check), storage (formData), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes
16
+ - Added comprehensive tests for the catch-all dispatch pattern
17
+
3
18
  ## 3.2.7
4
19
 
5
20
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -11,8 +11,15 @@ interface ObjectStackHonoOptions {
11
11
  declare function objectStackMiddleware(kernel: ObjectKernel): (c: any, next: any) => Promise<void>;
12
12
  /**
13
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.
14
+ *
15
+ * Only routes that need framework-specific handling (auth service, storage
16
+ * formData, GraphQL raw result, discovery wrapper) are registered explicitly.
17
+ * All other routes (meta, data, packages, analytics, automation, i18n, ui,
18
+ * openapi, custom endpoints, and any future routes) are handled by a
19
+ * catch-all that delegates to `HttpDispatcher.dispatch()`.
20
+ *
21
+ * This means new routes added to `HttpDispatcher` automatically work in
22
+ * every adapter without any adapter-side code changes.
16
23
  *
17
24
  * @example
18
25
  * ```ts
package/dist/index.d.ts CHANGED
@@ -11,8 +11,15 @@ interface ObjectStackHonoOptions {
11
11
  declare function objectStackMiddleware(kernel: ObjectKernel): (c: any, next: any) => Promise<void>;
12
12
  /**
13
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.
14
+ *
15
+ * Only routes that need framework-specific handling (auth service, storage
16
+ * formData, GraphQL raw result, discovery wrapper) are registered explicitly.
17
+ * All other routes (meta, data, packages, analytics, automation, i18n, ui,
18
+ * openapi, custom endpoints, and any future routes) are handled by a
19
+ * catch-all that delegates to `HttpDispatcher.dispatch()`.
20
+ *
21
+ * This means new routes added to `HttpDispatcher` automatically work in
22
+ * every adapter without any adapter-side code changes.
16
23
  *
17
24
  * @example
18
25
  * ```ts
package/dist/index.js CHANGED
@@ -106,39 +106,27 @@ function createHonoApp(options) {
106
106
  return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
107
107
  }
108
108
  });
109
- app.all(`${prefix}/meta/*`, async (c) => {
109
+ app.all(`${prefix}/storage/*`, async (c) => {
110
110
  try {
111
- const subPath = c.req.path.substring(`${prefix}/meta`.length);
111
+ const subPath = c.req.path.substring(`${prefix}/storage`.length);
112
112
  const method = c.req.method;
113
- let body = void 0;
114
- if (method === "PUT" || method === "POST") {
115
- body = await c.req.json().catch(() => ({}));
113
+ let file = void 0;
114
+ if (method === "POST" && subPath === "/upload") {
115
+ const formData = await c.req.formData();
116
+ file = formData.get("file");
116
117
  }
117
- const result = await dispatcher.handleMetadata(subPath, { request: c.req.raw }, method, body);
118
+ const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });
118
119
  return toResponse(c, result);
119
120
  } catch (err) {
120
121
  return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
121
122
  }
122
123
  });
123
- app.all(`${prefix}/meta`, async (c) => {
124
+ app.all(`${prefix}/*`, async (c) => {
124
125
  try {
126
+ const subPath = c.req.path.substring(prefix.length);
125
127
  const method = c.req.method;
126
128
  let body = void 0;
127
- if (method === "PUT" || method === "POST") {
128
- body = await c.req.json().catch(() => ({}));
129
- }
130
- const result = await dispatcher.handleMetadata("", { request: c.req.raw }, method, body);
131
- return toResponse(c, result);
132
- } catch (err) {
133
- return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
134
- }
135
- });
136
- app.all(`${prefix}/data/*`, async (c) => {
137
- try {
138
- const subPath = c.req.path.substring(`${prefix}/data`.length);
139
- const method = c.req.method;
140
- let body = {};
141
- if (method === "POST" || method === "PATCH") {
129
+ if (method === "POST" || method === "PUT" || method === "PATCH") {
142
130
  body = await c.req.json().catch(() => ({}));
143
131
  }
144
132
  const queryParams = {};
@@ -146,22 +134,7 @@ function createHonoApp(options) {
146
134
  url.searchParams.forEach((val, key) => {
147
135
  queryParams[key] = val;
148
136
  });
149
- const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: c.req.raw });
150
- return toResponse(c, result);
151
- } catch (err) {
152
- return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
153
- }
154
- });
155
- app.all(`${prefix}/storage/*`, async (c) => {
156
- try {
157
- const subPath = c.req.path.substring(`${prefix}/storage`.length);
158
- const method = c.req.method;
159
- let file = void 0;
160
- if (method === "POST" && subPath === "/upload") {
161
- const formData = await c.req.formData();
162
- file = formData.get("file");
163
- }
164
- const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });
137
+ const result = await dispatcher.dispatch(method, subPath, body, queryParams, { request: c.req.raw });
165
138
  return toResponse(c, result);
166
139
  } catch (err) {
167
140
  return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
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 { 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}`, async (c) => {\n return c.json({ data: await 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 (prefer async to support factory-based services)\n let authService: AuthService | null = null;\n try {\n if (typeof options.kernel.getServiceAsync === 'function') {\n authService = await options.kernel.getServiceAsync<AuthService>('auth');\n } else if (typeof options.kernel.getService === 'function') {\n authService = options.kernel.getService<AuthService>('auth');\n }\n } catch {\n // Service not registered — fall through to dispatcher\n authService = null;\n }\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":[]}
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 *\n * Only routes that need framework-specific handling (auth service, storage\n * formData, GraphQL raw result, discovery wrapper) are registered explicitly.\n * All other routes (meta, data, packages, analytics, automation, i18n, ui,\n * openapi, custom endpoints, and any future routes) are handled by a\n * catch-all that delegates to `HttpDispatcher.dispatch()`.\n *\n * This means new routes added to `HttpDispatcher` automatically work in\n * every adapter without any adapter-side code changes.\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 // ─── Explicit routes (framework-specific handling required) ────────────────\n\n // --- Discovery ---\n app.get(`${prefix}`, async (c) => {\n return c.json({ data: await dispatcher.getDiscoveryInfo(prefix) });\n });\n\n // --- .well-known ---\n app.get('/.well-known/objectstack', (c) => {\n return c.redirect(prefix);\n });\n\n // --- Auth (needs auth service integration) ---\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 (prefer async to support factory-based services)\n let authService: AuthService | null = null;\n try {\n if (typeof options.kernel.getServiceAsync === 'function') {\n authService = await options.kernel.getServiceAsync<AuthService>('auth');\n } else if (typeof options.kernel.getService === 'function') {\n authService = options.kernel.getService<AuthService>('auth');\n }\n } catch {\n // Service not registered — fall through to dispatcher\n authService = null;\n }\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 (returns raw result, not HttpDispatcherResult) ---\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 // --- Storage (needs formData parsing) ---\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 // ─── Catch-all: delegate to dispatcher.dispatch() ─────────────────────────\n // Handles meta, data, packages, analytics, automation, i18n, ui, openapi,\n // custom API endpoints, and any future routes added to HttpDispatcher.\n app.all(`${prefix}/*`, async (c) => {\n try {\n const subPath = c.req.path.substring(prefix.length);\n const method = c.req.method;\n\n let body: any = undefined;\n if (method === 'POST' || method === 'PUT' || 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.dispatch(method, subPath, 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 return app;\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -81,39 +81,27 @@ function createHonoApp(options) {
81
81
  return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
82
82
  }
83
83
  });
84
- app.all(`${prefix}/meta/*`, async (c) => {
84
+ app.all(`${prefix}/storage/*`, async (c) => {
85
85
  try {
86
- const subPath = c.req.path.substring(`${prefix}/meta`.length);
86
+ const subPath = c.req.path.substring(`${prefix}/storage`.length);
87
87
  const method = c.req.method;
88
- let body = void 0;
89
- if (method === "PUT" || method === "POST") {
90
- body = await c.req.json().catch(() => ({}));
88
+ let file = void 0;
89
+ if (method === "POST" && subPath === "/upload") {
90
+ const formData = await c.req.formData();
91
+ file = formData.get("file");
91
92
  }
92
- const result = await dispatcher.handleMetadata(subPath, { request: c.req.raw }, method, body);
93
+ const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });
93
94
  return toResponse(c, result);
94
95
  } catch (err) {
95
96
  return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
96
97
  }
97
98
  });
98
- app.all(`${prefix}/meta`, async (c) => {
99
+ app.all(`${prefix}/*`, async (c) => {
99
100
  try {
101
+ const subPath = c.req.path.substring(prefix.length);
100
102
  const method = c.req.method;
101
103
  let body = void 0;
102
- if (method === "PUT" || method === "POST") {
103
- body = await c.req.json().catch(() => ({}));
104
- }
105
- const result = await dispatcher.handleMetadata("", { request: c.req.raw }, method, body);
106
- return toResponse(c, result);
107
- } catch (err) {
108
- return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
109
- }
110
- });
111
- app.all(`${prefix}/data/*`, async (c) => {
112
- try {
113
- const subPath = c.req.path.substring(`${prefix}/data`.length);
114
- const method = c.req.method;
115
- let body = {};
116
- if (method === "POST" || method === "PATCH") {
104
+ if (method === "POST" || method === "PUT" || method === "PATCH") {
117
105
  body = await c.req.json().catch(() => ({}));
118
106
  }
119
107
  const queryParams = {};
@@ -121,22 +109,7 @@ function createHonoApp(options) {
121
109
  url.searchParams.forEach((val, key) => {
122
110
  queryParams[key] = val;
123
111
  });
124
- const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: c.req.raw });
125
- return toResponse(c, result);
126
- } catch (err) {
127
- return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
128
- }
129
- });
130
- app.all(`${prefix}/storage/*`, async (c) => {
131
- try {
132
- const subPath = c.req.path.substring(`${prefix}/storage`.length);
133
- const method = c.req.method;
134
- let file = void 0;
135
- if (method === "POST" && subPath === "/upload") {
136
- const formData = await c.req.formData();
137
- file = formData.get("file");
138
- }
139
- const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });
112
+ const result = await dispatcher.dispatch(method, subPath, body, queryParams, { request: c.req.raw });
140
113
  return toResponse(c, result);
141
114
  } catch (err) {
142
115
  return errorJson(c, err.message || "Internal Server Error", err.statusCode || 500);
@@ -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 { 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}`, async (c) => {\n return c.json({ data: await 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 (prefer async to support factory-based services)\n let authService: AuthService | null = null;\n try {\n if (typeof options.kernel.getServiceAsync === 'function') {\n authService = await options.kernel.getServiceAsync<AuthService>('auth');\n } else if (typeof options.kernel.getService === 'function') {\n authService = options.kernel.getService<AuthService>('auth');\n }\n } catch {\n // Service not registered — fall through to dispatcher\n authService = null;\n }\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":[]}
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 *\n * Only routes that need framework-specific handling (auth service, storage\n * formData, GraphQL raw result, discovery wrapper) are registered explicitly.\n * All other routes (meta, data, packages, analytics, automation, i18n, ui,\n * openapi, custom endpoints, and any future routes) are handled by a\n * catch-all that delegates to `HttpDispatcher.dispatch()`.\n *\n * This means new routes added to `HttpDispatcher` automatically work in\n * every adapter without any adapter-side code changes.\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 // ─── Explicit routes (framework-specific handling required) ────────────────\n\n // --- Discovery ---\n app.get(`${prefix}`, async (c) => {\n return c.json({ data: await dispatcher.getDiscoveryInfo(prefix) });\n });\n\n // --- .well-known ---\n app.get('/.well-known/objectstack', (c) => {\n return c.redirect(prefix);\n });\n\n // --- Auth (needs auth service integration) ---\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 (prefer async to support factory-based services)\n let authService: AuthService | null = null;\n try {\n if (typeof options.kernel.getServiceAsync === 'function') {\n authService = await options.kernel.getServiceAsync<AuthService>('auth');\n } else if (typeof options.kernel.getService === 'function') {\n authService = options.kernel.getService<AuthService>('auth');\n }\n } catch {\n // Service not registered — fall through to dispatcher\n authService = null;\n }\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 (returns raw result, not HttpDispatcherResult) ---\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 // --- Storage (needs formData parsing) ---\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 // ─── Catch-all: delegate to dispatcher.dispatch() ─────────────────────────\n // Handles meta, data, packages, analytics, automation, i18n, ui, openapi,\n // custom API endpoints, and any future routes added to HttpDispatcher.\n app.all(`${prefix}/*`, async (c) => {\n try {\n const subPath = c.req.path.substring(prefix.length);\n const method = c.req.method;\n\n let body: any = undefined;\n if (method === 'POST' || method === 'PUT' || 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.dispatch(method, subPath, 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 return app;\n}\n"],"mappings":";AAEA,SAAS,YAAY;AACrB,SAA4B,sBAA4C;AAiBjE,SAAS,sBAAsB,QAAsB;AAC1D,SAAO,OAAO,GAAQ,SAAc;AAClC,MAAE,IAAI,eAAe,MAAM;AAC3B,UAAM,KAAK;AAAA,EACb;AACF;AAqBO,SAAS,cAAc,SAAuC;AACnE,QAAM,MAAM,IAAI,KAAK;AACrB,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,aAAa,IAAI,eAAe,QAAQ,MAAM;AAEpD,QAAM,YAAY,CAAC,GAAQ,SAAiB,OAAe,QAAQ;AACjE,WAAO,EAAE,KAAK,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG,IAAI;AAAA,EAClE;AAEA,QAAM,aAAa,CAAC,GAAQ,WAAiC;AAC3D,QAAI,OAAO,SAAS;AAClB,UAAI,OAAO,UAAU;AACnB,YAAI,OAAO,SAAS,SAAS;AAC3B,iBAAO,QAAQ,OAAO,SAAS,OAAO,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,CAAW,CAAC;AAAA,QACtF;AACA,eAAO,EAAE,KAAK,OAAO,SAAS,MAAM,OAAO,SAAS,MAAM;AAAA,MAC5D;AACA,UAAI,OAAO,QAAQ;AACjB,cAAM,MAAM,OAAO;AACnB,YAAI,IAAI,SAAS,cAAc,IAAI,KAAK;AACtC,iBAAO,EAAE,SAAS,IAAI,GAAG;AAAA,QAC3B;AACA,YAAI,IAAI,SAAS,YAAY,IAAI,QAAQ;AACvC,cAAI,IAAI,SAAS;AACf,mBAAO,QAAQ,IAAI,OAAO,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,CAAW,CAAC;AAAA,UAC1E;AACA,iBAAO,IAAI,SAAS,IAAI,QAAQ,EAAE,QAAQ,IAAI,CAAC;AAAA,QACjD;AACA,eAAO,EAAE,KAAK,KAAK,GAAG;AAAA,MACxB;AAAA,IACF;AACA,WAAO,UAAU,GAAG,aAAa,GAAG;AAAA,EACtC;AAKA,MAAI,IAAI,GAAG,MAAM,IAAI,OAAO,MAAM;AAChC,WAAO,EAAE,KAAK,EAAE,MAAM,MAAM,WAAW,iBAAiB,MAAM,EAAE,CAAC;AAAA,EACnE,CAAC;AAGD,MAAI,IAAI,4BAA4B,CAAC,MAAM;AACzC,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B,CAAC;AAGD,MAAI,IAAI,GAAG,MAAM,WAAW,OAAO,MAAM;AACvC,QAAI;AACF,YAAM,OAAO,EAAE,IAAI,KAAK,UAAU,GAAG,MAAM,SAAS,MAAM;AAC1D,YAAM,SAAS,EAAE,IAAI;AAGrB,UAAI,cAAkC;AACtC,UAAI;AACF,YAAI,OAAO,QAAQ,OAAO,oBAAoB,YAAY;AACxD,wBAAc,MAAM,QAAQ,OAAO,gBAA6B,MAAM;AAAA,QACxE,WAAW,OAAO,QAAQ,OAAO,eAAe,YAAY;AAC1D,wBAAc,QAAQ,OAAO,WAAwB,MAAM;AAAA,QAC7D;AAAA,MACF,QAAQ;AAEN,sBAAc;AAAA,MAChB;AAEA,UAAI,eAAe,OAAO,YAAY,kBAAkB,YAAY;AAClE,cAAM,WAAW,MAAM,YAAY,cAAc,EAAE,IAAI,GAAG;AAC1D,eAAO,IAAI,SAAS,SAAS,MAAM;AAAA,UACjC,QAAQ,SAAS;AAAA,UACjB,SAAS,SAAS;AAAA,QACpB,CAAC;AAAA,MACH;AAGA,YAAM,OAAO,WAAW,SAAS,WAAW,SACxC,CAAC,IACD,MAAM,EAAE,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACvC,YAAM,SAAS,MAAM,WAAW,WAAW,MAAM,QAAQ,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC;AACrF,aAAO,WAAW,GAAG,MAAM;AAAA,IAC7B,SAAS,KAAU;AACjB,aAAO,UAAU,GAAG,IAAI,WAAW,yBAAyB,IAAI,cAAc,GAAG;AAAA,IACnF;AAAA,EACF,CAAC;AAGD,MAAI,KAAK,GAAG,MAAM,YAAY,OAAO,MAAM;AACzC,QAAI;AACF,YAAM,OAAO,MAAM,EAAE,IAAI,KAAK;AAC9B,YAAM,SAAS,MAAM,WAAW,cAAc,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC;AAC1E,aAAO,EAAE,KAAK,MAAM;AAAA,IACtB,SAAS,KAAU;AACjB,aAAO,UAAU,GAAG,IAAI,WAAW,yBAAyB,IAAI,cAAc,GAAG;AAAA,IACnF;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,GAAG,MAAM,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,YAAM,UAAU,EAAE,IAAI,KAAK,UAAU,GAAG,MAAM,WAAW,MAAM;AAC/D,YAAM,SAAS,EAAE,IAAI;AAErB,UAAI,OAAY;AAChB,UAAI,WAAW,UAAU,YAAY,WAAW;AAC9C,cAAM,WAAW,MAAM,EAAE,IAAI,SAAS;AACtC,eAAO,SAAS,IAAI,MAAM;AAAA,MAC5B;AAEA,YAAM,SAAS,MAAM,WAAW,cAAc,SAAS,QAAQ,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC;AAC3F,aAAO,WAAW,GAAG,MAAM;AAAA,IAC7B,SAAS,KAAU;AACjB,aAAO,UAAU,GAAG,IAAI,WAAW,yBAAyB,IAAI,cAAc,GAAG;AAAA,IACnF;AAAA,EACF,CAAC;AAKD,MAAI,IAAI,GAAG,MAAM,MAAM,OAAO,MAAM;AAClC,QAAI;AACF,YAAM,UAAU,EAAE,IAAI,KAAK,UAAU,OAAO,MAAM;AAClD,YAAM,SAAS,EAAE,IAAI;AAErB,UAAI,OAAY;AAChB,UAAI,WAAW,UAAU,WAAW,SAAS,WAAW,SAAS;AAC/D,eAAO,MAAM,EAAE,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAAA,MAC5C;AAEA,YAAM,cAAmC,CAAC;AAC1C,YAAM,MAAM,IAAI,IAAI,EAAE,IAAI,GAAG;AAC7B,UAAI,aAAa,QAAQ,CAAC,KAAK,QAAQ;AAAE,oBAAY,GAAG,IAAI;AAAA,MAAK,CAAC;AAElE,YAAM,SAAS,MAAM,WAAW,SAAS,QAAQ,SAAS,MAAM,aAAa,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC;AACnG,aAAO,WAAW,GAAG,MAAM;AAAA,IAC7B,SAAS,KAAU;AACjB,aAAO,UAAU,GAAG,IAAI,WAAW,yBAAyB,IAAI,cAAc,GAAG;AAAA,IACnF;AAAA,EACF,CAAC;AAED,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/hono",
3
- "version": "3.2.7",
3
+ "version": "3.2.8",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,14 +12,14 @@
12
12
  }
13
13
  },
14
14
  "peerDependencies": {
15
- "hono": "^4.12.5",
16
- "@objectstack/runtime": "^3.2.7"
15
+ "hono": "^4.12.8",
16
+ "@objectstack/runtime": "^3.2.8"
17
17
  },
18
18
  "devDependencies": {
19
- "hono": "^4.12.5",
19
+ "hono": "^4.12.8",
20
20
  "typescript": "^5.0.0",
21
- "vitest": "^4.0.18",
22
- "@objectstack/runtime": "3.2.7"
21
+ "vitest": "^4.1.0",
22
+ "@objectstack/runtime": "3.2.8"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup --config ../../../tsup.config.ts",
package/src/hono.test.ts CHANGED
@@ -8,9 +8,8 @@ const mockDispatcher = {
8
8
  getDiscoveryInfo: vi.fn().mockReturnValue({ version: '1.0', endpoints: [] }),
9
9
  handleAuth: vi.fn().mockResolvedValue({ handled: true, response: { body: { ok: true }, status: 200 } }),
10
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
11
  handleStorage: vi.fn().mockResolvedValue({ handled: true, response: { body: {}, status: 200 } }),
12
+ dispatch: vi.fn().mockResolvedValue({ handled: true, response: { body: { success: true }, status: 200 } }),
14
13
  };
15
14
 
16
15
  vi.mock('@objectstack/runtime', () => {
@@ -268,21 +267,20 @@ describe('createHonoApp', () => {
268
267
  });
269
268
  });
270
269
 
271
- describe('Metadata Endpoint', () => {
272
- it('GET /api/meta/objects calls handleMetadata', async () => {
270
+ describe('Catch-all Dispatch', () => {
271
+ it('GET /api/meta/objects delegates to dispatch()', async () => {
273
272
  const res = await app.request('/api/meta/objects');
274
273
  expect(res.status).toBe(200);
275
- const json = await res.json();
276
- expect(json.objects).toBeDefined();
277
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
278
- '/objects',
279
- expect.objectContaining({ request: expect.anything() }),
274
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
280
275
  'GET',
276
+ '/meta/objects',
281
277
  undefined,
278
+ expect.any(Object),
279
+ expect.objectContaining({ request: expect.anything() }),
282
280
  );
283
281
  });
284
282
 
285
- it('PUT /api/meta/objects parses JSON body', async () => {
283
+ it('PUT /api/meta/objects parses JSON body via dispatch()', async () => {
286
284
  const body = { name: 'test_object' };
287
285
  const res = await app.request('/api/meta/objects', {
288
286
  method: 'PUT',
@@ -290,36 +288,46 @@ describe('createHonoApp', () => {
290
288
  body: JSON.stringify(body),
291
289
  });
292
290
  expect(res.status).toBe(200);
293
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
294
- '/objects',
295
- expect.objectContaining({ request: expect.anything() }),
291
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
296
292
  'PUT',
293
+ '/meta/objects',
297
294
  body,
295
+ expect.any(Object),
296
+ expect.objectContaining({ request: expect.anything() }),
298
297
  );
299
298
  });
300
299
 
301
300
  it('GET /api/meta with no trailing path', async () => {
302
301
  const res = await app.request('/api/meta');
303
302
  expect(res.status).toBe(200);
304
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
305
- '',
303
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
304
+ 'GET',
305
+ '/meta',
306
+ undefined,
307
+ expect.any(Object),
306
308
  expect.objectContaining({ request: expect.anything() }),
309
+ );
310
+ });
311
+
312
+ it('forwards query parameters through dispatch()', async () => {
313
+ const res = await app.request('/api/meta/objects?package=com.acme.crm');
314
+ expect(res.status).toBe(200);
315
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
307
316
  'GET',
317
+ '/meta/objects',
308
318
  undefined,
319
+ expect.objectContaining({ package: 'com.acme.crm' }),
320
+ expect.objectContaining({ request: expect.anything() }),
309
321
  );
310
322
  });
311
- });
312
323
 
313
- describe('Data Endpoint', () => {
314
- it('GET /api/data/account calls handleData', async () => {
324
+ it('GET /api/data/account delegates to dispatch()', async () => {
315
325
  const res = await app.request('/api/data/account');
316
326
  expect(res.status).toBe(200);
317
- const json = await res.json();
318
- expect(json.records).toBeDefined();
319
- expect(mockDispatcher.handleData).toHaveBeenCalledWith(
320
- '/account',
327
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
321
328
  'GET',
322
- {},
329
+ '/data/account',
330
+ undefined,
323
331
  expect.any(Object),
324
332
  expect.objectContaining({ request: expect.anything() }),
325
333
  );
@@ -333,9 +341,9 @@ describe('createHonoApp', () => {
333
341
  body: JSON.stringify(body),
334
342
  });
335
343
  expect(res.status).toBe(200);
336
- expect(mockDispatcher.handleData).toHaveBeenCalledWith(
337
- '/account',
344
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
338
345
  'POST',
346
+ '/data/account',
339
347
  body,
340
348
  expect.any(Object),
341
349
  expect.objectContaining({ request: expect.anything() }),
@@ -350,32 +358,129 @@ describe('createHonoApp', () => {
350
358
  body: JSON.stringify(body),
351
359
  });
352
360
  expect(res.status).toBe(200);
353
- expect(mockDispatcher.handleData).toHaveBeenCalledWith(
354
- '/account',
361
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
355
362
  'PATCH',
363
+ '/data/account',
356
364
  body,
357
365
  expect.any(Object),
358
366
  expect.objectContaining({ request: expect.anything() }),
359
367
  );
360
368
  });
361
369
 
362
- it('returns 404 when result is not handled', async () => {
363
- mockDispatcher.handleData.mockResolvedValueOnce({ handled: false });
370
+ it('returns 404 when dispatch result is not handled', async () => {
371
+ mockDispatcher.dispatch.mockResolvedValueOnce({ handled: false });
364
372
  const res = await app.request('/api/data/missing');
365
373
  expect(res.status).toBe(404);
366
374
  const json = await res.json();
367
375
  expect(json.success).toBe(false);
368
376
  });
369
- });
370
377
 
371
- describe('Storage Endpoint', () => {
372
- it('GET /api/storage/files calls handleStorage', async () => {
373
- const res = await app.request('/api/storage/files');
378
+ it('GET /api/packages delegates to dispatch()', async () => {
379
+ const res = await app.request('/api/packages');
380
+ expect(res.status).toBe(200);
381
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
382
+ 'GET',
383
+ '/packages',
384
+ undefined,
385
+ expect.any(Object),
386
+ expect.objectContaining({ request: expect.anything() }),
387
+ );
388
+ });
389
+
390
+ it('GET /api/packages/:id delegates to dispatch()', async () => {
391
+ const res = await app.request('/api/packages/com.acme.crm');
392
+ expect(res.status).toBe(200);
393
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
394
+ 'GET',
395
+ '/packages/com.acme.crm',
396
+ undefined,
397
+ expect.any(Object),
398
+ expect.objectContaining({ request: expect.anything() }),
399
+ );
400
+ });
401
+
402
+ it('POST /api/packages parses JSON body', async () => {
403
+ const body = { manifest: { name: 'test-pkg' } };
404
+ const res = await app.request('/api/packages', {
405
+ method: 'POST',
406
+ headers: { 'Content-Type': 'application/json' },
407
+ body: JSON.stringify(body),
408
+ });
409
+ expect(res.status).toBe(200);
410
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
411
+ 'POST',
412
+ '/packages',
413
+ body,
414
+ expect.any(Object),
415
+ expect.objectContaining({ request: expect.anything() }),
416
+ );
417
+ });
418
+
419
+ it('GET /api/packages?status=active forwards query params', async () => {
420
+ const res = await app.request('/api/packages?status=active');
374
421
  expect(res.status).toBe(200);
375
- expect(mockDispatcher.handleStorage).toHaveBeenCalledWith(
376
- '/files',
422
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
377
423
  'GET',
424
+ '/packages',
378
425
  undefined,
426
+ expect.objectContaining({ status: 'active' }),
427
+ expect.objectContaining({ request: expect.anything() }),
428
+ );
429
+ });
430
+
431
+ it('returns error on dispatch exception', async () => {
432
+ mockDispatcher.dispatch.mockRejectedValueOnce(new Error('Service unavailable'));
433
+ const res = await app.request('/api/packages');
434
+ expect(res.status).toBe(500);
435
+ const json = await res.json();
436
+ expect(json.success).toBe(false);
437
+ expect(json.error.message).toBe('Service unavailable');
438
+ });
439
+
440
+ it('GET /api/analytics delegates to dispatch()', async () => {
441
+ const res = await app.request('/api/analytics');
442
+ expect(res.status).toBe(200);
443
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
444
+ 'GET',
445
+ '/analytics',
446
+ undefined,
447
+ expect.any(Object),
448
+ expect.objectContaining({ request: expect.anything() }),
449
+ );
450
+ });
451
+
452
+ it('GET /api/automation delegates to dispatch()', async () => {
453
+ const res = await app.request('/api/automation');
454
+ expect(res.status).toBe(200);
455
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
456
+ 'GET',
457
+ '/automation',
458
+ undefined,
459
+ expect.any(Object),
460
+ expect.objectContaining({ request: expect.anything() }),
461
+ );
462
+ });
463
+
464
+ it('GET /api/i18n delegates to dispatch()', async () => {
465
+ const res = await app.request('/api/i18n');
466
+ expect(res.status).toBe(200);
467
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
468
+ 'GET',
469
+ '/i18n',
470
+ undefined,
471
+ expect.any(Object),
472
+ expect.objectContaining({ request: expect.anything() }),
473
+ );
474
+ });
475
+
476
+ it('GET /api/ui/view/account delegates to dispatch()', async () => {
477
+ const res = await app.request('/api/ui/view/account');
478
+ expect(res.status).toBe(200);
479
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
480
+ 'GET',
481
+ '/ui/view/account',
482
+ undefined,
483
+ expect.any(Object),
379
484
  expect.objectContaining({ request: expect.anything() }),
380
485
  );
381
486
  });
@@ -383,7 +488,7 @@ describe('createHonoApp', () => {
383
488
 
384
489
  describe('Error Handling', () => {
385
490
  it('returns 500 with default message on generic error', async () => {
386
- mockDispatcher.handleData.mockRejectedValueOnce(new Error());
491
+ mockDispatcher.dispatch.mockRejectedValueOnce(new Error());
387
492
  const res = await app.request('/api/data/account');
388
493
  expect(res.status).toBe(500);
389
494
  const json = await res.json();
@@ -391,7 +496,7 @@ describe('createHonoApp', () => {
391
496
  });
392
497
 
393
498
  it('uses custom statusCode from error', async () => {
394
- mockDispatcher.handleData.mockRejectedValueOnce(
499
+ mockDispatcher.dispatch.mockRejectedValueOnce(
395
500
  Object.assign(new Error('Forbidden'), { statusCode: 403 }),
396
501
  );
397
502
  const res = await app.request('/api/data/account');
@@ -403,7 +508,7 @@ describe('createHonoApp', () => {
403
508
 
404
509
  describe('toResponse', () => {
405
510
  it('handles redirect result', async () => {
406
- mockDispatcher.handleData.mockResolvedValueOnce({
511
+ mockDispatcher.dispatch.mockResolvedValueOnce({
407
512
  handled: true,
408
513
  result: { type: 'redirect', url: 'https://example.com' },
409
514
  });
@@ -413,7 +518,7 @@ describe('createHonoApp', () => {
413
518
  });
414
519
 
415
520
  it('handles generic result objects with 200 status', async () => {
416
- mockDispatcher.handleData.mockResolvedValueOnce({
521
+ mockDispatcher.dispatch.mockResolvedValueOnce({
417
522
  handled: true,
418
523
  result: { foo: 'bar' },
419
524
  });
@@ -424,7 +529,7 @@ describe('createHonoApp', () => {
424
529
  });
425
530
 
426
531
  it('sets custom headers from response', async () => {
427
- mockDispatcher.handleData.mockResolvedValueOnce({
532
+ mockDispatcher.dispatch.mockResolvedValueOnce({
428
533
  handled: true,
429
534
  response: { status: 201, body: { id: 1 }, headers: { 'X-Custom': 'yes' } },
430
535
  });
@@ -439,4 +544,138 @@ describe('createHonoApp', () => {
439
544
  expect(json.id).toBe(1);
440
545
  });
441
546
  });
547
+
548
+ describe('Vercel Delegation Pattern (api/index.ts → inner.fetch)', () => {
549
+ /**
550
+ * Helper: creates the same outer→inner delegation pattern used by
551
+ * `apps/studio/api/index.ts`. The outer Hono app delegates all
552
+ * requests to the inner ObjectStack Hono app via `inner.fetch()`.
553
+ */
554
+ function createVercelApp() {
555
+ const innerApp = createHonoApp({ kernel: mockKernel, prefix: '/api/v1' });
556
+ const outerApp = new Hono();
557
+ outerApp.all('*', async (c) => {
558
+ return innerApp.fetch(c.req.raw);
559
+ });
560
+ return outerApp;
561
+ }
562
+
563
+ it('works when an outer Hono app delegates via inner.fetch(c.req.raw)', async () => {
564
+ const outerApp = createVercelApp();
565
+
566
+ const res = await outerApp.request('/api/v1/meta');
567
+ expect(res.status).toBe(200);
568
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
569
+ 'GET',
570
+ '/meta',
571
+ undefined,
572
+ expect.any(Object),
573
+ expect.objectContaining({ request: expect.anything() }),
574
+ );
575
+ });
576
+
577
+ it('routes /api/v1/packages through outer→inner delegation', async () => {
578
+ const outerApp = createVercelApp();
579
+
580
+ const res = await outerApp.request('/api/v1/packages');
581
+ expect(res.status).toBe(200);
582
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
583
+ 'GET',
584
+ '/packages',
585
+ undefined,
586
+ expect.any(Object),
587
+ expect.objectContaining({ request: expect.anything() }),
588
+ );
589
+ });
590
+
591
+ it('routes /api/v1 discovery through outer→inner delegation', async () => {
592
+ const outerApp = createVercelApp();
593
+
594
+ const res = await outerApp.request('/api/v1');
595
+ expect(res.status).toBe(200);
596
+ const json = await res.json();
597
+ expect(json.data).toBeDefined();
598
+ expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api/v1');
599
+ });
600
+
601
+ it('routes /api/v1/data/account through outer→inner delegation', async () => {
602
+ const outerApp = createVercelApp();
603
+
604
+ const res = await outerApp.request('/api/v1/data/account');
605
+ expect(res.status).toBe(200);
606
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
607
+ 'GET',
608
+ '/data/account',
609
+ undefined,
610
+ expect.any(Object),
611
+ expect.objectContaining({ request: expect.anything() }),
612
+ );
613
+ });
614
+
615
+ it('returns 500 with error details when inner app throws', async () => {
616
+ const outerApp = new Hono();
617
+
618
+ outerApp.all('*', async (c) => {
619
+ try {
620
+ // Simulate a kernel boot failure
621
+ throw new Error('Kernel boot failed');
622
+ } catch (err: any) {
623
+ return c.json(
624
+ { success: false, error: { message: err.message, code: 500 } },
625
+ 500,
626
+ );
627
+ }
628
+ });
629
+
630
+ const res = await outerApp.request('/api/v1/meta');
631
+ expect(res.status).toBe(500);
632
+ const json = await res.json();
633
+ expect(json.success).toBe(false);
634
+ expect(json.error.message).toBe('Kernel boot failed');
635
+ });
636
+ });
637
+
638
+ describe('Vercel deployment endpoint smoke tests', () => {
639
+ /**
640
+ * These tests validate that the two key deployment-health endpoints
641
+ * `/api/v1/meta` and `/api/v1/packages` return 200 OK when routed
642
+ * through the Vercel adapter pattern (outer Hono → inner ObjectStack Hono).
643
+ */
644
+ let outerApp: Hono;
645
+
646
+ beforeEach(() => {
647
+ vi.clearAllMocks();
648
+ const innerApp = createHonoApp({ kernel: mockKernel, prefix: '/api/v1' });
649
+ outerApp = new Hono();
650
+ outerApp.all('*', async (c) => innerApp.fetch(c.req.raw));
651
+ });
652
+
653
+ it('GET /api/v1/meta returns 200 OK', async () => {
654
+ const res = await outerApp.request('/api/v1/meta');
655
+ expect(res.status).toBe(200);
656
+ const json = await res.json();
657
+ expect(json.success).toBe(true);
658
+ });
659
+
660
+ it('GET /api/v1/meta/object returns 200 OK', async () => {
661
+ const res = await outerApp.request('/api/v1/meta/object');
662
+ expect(res.status).toBe(200);
663
+ const json = await res.json();
664
+ expect(json.success).toBe(true);
665
+ });
666
+
667
+ it('GET /api/v1/packages returns 200 OK', async () => {
668
+ const res = await outerApp.request('/api/v1/packages');
669
+ expect(res.status).toBe(200);
670
+ const json = await res.json();
671
+ expect(json.success).toBe(true);
672
+ });
673
+
674
+ it('GET /api/v1/packages/:id returns 200 OK', async () => {
675
+ const res = await outerApp.request('/api/v1/packages/com.acme.crm');
676
+ expect(res.status).toBe(200);
677
+ const json = await res.json();
678
+ expect(json.success).toBe(true);
679
+ });
680
+ });
442
681
  });
package/src/index.ts CHANGED
@@ -27,8 +27,15 @@ export function objectStackMiddleware(kernel: ObjectKernel) {
27
27
 
28
28
  /**
29
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.
30
+ *
31
+ * Only routes that need framework-specific handling (auth service, storage
32
+ * formData, GraphQL raw result, discovery wrapper) are registered explicitly.
33
+ * All other routes (meta, data, packages, analytics, automation, i18n, ui,
34
+ * openapi, custom endpoints, and any future routes) are handled by a
35
+ * catch-all that delegates to `HttpDispatcher.dispatch()`.
36
+ *
37
+ * This means new routes added to `HttpDispatcher` automatically work in
38
+ * every adapter without any adapter-side code changes.
32
39
  *
33
40
  * @example
34
41
  * ```ts
@@ -71,6 +78,8 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
71
78
  return errorJson(c, 'Not Found', 404);
72
79
  };
73
80
 
81
+ // ─── Explicit routes (framework-specific handling required) ────────────────
82
+
74
83
  // --- Discovery ---
75
84
  app.get(`${prefix}`, async (c) => {
76
85
  return c.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
@@ -81,7 +90,7 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
81
90
  return c.redirect(prefix);
82
91
  });
83
92
 
84
- // --- Auth ---
93
+ // --- Auth (needs auth service integration) ---
85
94
  app.all(`${prefix}/auth/*`, async (c) => {
86
95
  try {
87
96
  const path = c.req.path.substring(`${prefix}/auth/`.length);
@@ -119,7 +128,7 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
119
128
  }
120
129
  });
121
130
 
122
- // --- GraphQL ---
131
+ // --- GraphQL (returns raw result, not HttpDispatcherResult) ---
123
132
  app.post(`${prefix}/graphql`, async (c) => {
124
133
  try {
125
134
  const body = await c.req.json();
@@ -130,47 +139,35 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
130
139
  }
131
140
  });
132
141
 
133
- // --- Metadata ---
134
- app.all(`${prefix}/meta/*`, async (c) => {
142
+ // --- Storage (needs formData parsing) ---
143
+ app.all(`${prefix}/storage/*`, async (c) => {
135
144
  try {
136
- const subPath = c.req.path.substring(`${prefix}/meta`.length);
145
+ const subPath = c.req.path.substring(`${prefix}/storage`.length);
137
146
  const method = c.req.method;
138
147
 
139
- let body: any = undefined;
140
- if (method === 'PUT' || method === 'POST') {
141
- body = await c.req.json().catch(() => ({}));
148
+ let file: any = undefined;
149
+ if (method === 'POST' && subPath === '/upload') {
150
+ const formData = await c.req.formData();
151
+ file = formData.get('file');
142
152
  }
143
153
 
144
- const result = await dispatcher.handleMetadata(subPath, { request: c.req.raw }, method, body);
145
- return toResponse(c, result);
146
- } catch (err: any) {
147
- return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
148
- }
149
- });
150
-
151
- // Also handle /meta with no trailing path
152
- app.all(`${prefix}/meta`, async (c) => {
153
- try {
154
- const method = c.req.method;
155
- let body: any = undefined;
156
- if (method === 'PUT' || method === 'POST') {
157
- body = await c.req.json().catch(() => ({}));
158
- }
159
- const result = await dispatcher.handleMetadata('', { request: c.req.raw }, method, body);
154
+ const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });
160
155
  return toResponse(c, result);
161
156
  } catch (err: any) {
162
157
  return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
163
158
  }
164
159
  });
165
160
 
166
- // --- Data ---
167
- app.all(`${prefix}/data/*`, async (c) => {
161
+ // ─── Catch-all: delegate to dispatcher.dispatch() ─────────────────────────
162
+ // Handles meta, data, packages, analytics, automation, i18n, ui, openapi,
163
+ // custom API endpoints, and any future routes added to HttpDispatcher.
164
+ app.all(`${prefix}/*`, async (c) => {
168
165
  try {
169
- const subPath = c.req.path.substring(`${prefix}/data`.length);
166
+ const subPath = c.req.path.substring(prefix.length);
170
167
  const method = c.req.method;
171
168
 
172
- let body: any = {};
173
- if (method === 'POST' || method === 'PATCH') {
169
+ let body: any = undefined;
170
+ if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
174
171
  body = await c.req.json().catch(() => ({}));
175
172
  }
176
173
 
@@ -178,26 +175,7 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
178
175
  const url = new URL(c.req.url);
179
176
  url.searchParams.forEach((val, key) => { queryParams[key] = val; });
180
177
 
181
- const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: c.req.raw });
182
- return toResponse(c, result);
183
- } catch (err: any) {
184
- return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);
185
- }
186
- });
187
-
188
- // --- Storage ---
189
- app.all(`${prefix}/storage/*`, async (c) => {
190
- try {
191
- const subPath = c.req.path.substring(`${prefix}/storage`.length);
192
- const method = c.req.method;
193
-
194
- let file: any = undefined;
195
- if (method === 'POST' && subPath === '/upload') {
196
- const formData = await c.req.formData();
197
- file = formData.get('file');
198
- }
199
-
200
- const result = await dispatcher.handleStorage(subPath, method, file, { request: c.req.raw });
178
+ const result = await dispatcher.dispatch(method, subPath, body, queryParams, { request: c.req.raw });
201
179
  return toResponse(c, result);
202
180
  } catch (err: any) {
203
181
  return errorJson(c, err.message || 'Internal Server Error', err.statusCode || 500);