@objectstack/hono 4.0.4 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,19 +1,94 @@
1
1
  # @objectstack/hono
2
2
 
3
- The official Hono adapter for ObjectStack.
3
+ > Hono adapter for ObjectStack — edge-compatible REST API server for Cloudflare Workers, Deno, Bun, Vercel Edge, and Node.
4
4
 
5
- ## Features
6
- - Lightweight & Fast
7
- - Edge Compatible (Cloudflare Workers, Deno, Bun)
8
- - Built-in CORS
5
+ [![npm](https://img.shields.io/npm/v/@objectstack/hono.svg)](https://www.npmjs.com/package/@objectstack/hono)
6
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
9
7
 
10
- ## Usage
8
+ ## Overview
9
+
10
+ Creates a ready-to-serve Hono app that routes all ObjectStack traffic through `HttpDispatcher`. Built-in CORS with wildcard origin pattern support (`https://*.example.com`, `http://localhost:*`), compatible with better-auth bearer token rotation (automatically exposes `set-auth-token`).
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @objectstack/hono hono
16
+ ```
17
+
18
+ ## Quick Start
11
19
 
12
20
  ```typescript
13
21
  import { createHonoApp } from '@objectstack/hono';
14
22
  import { kernel } from './my-kernel';
15
23
 
16
- const app = createHonoApp({ kernel });
24
+ const app = createHonoApp({
25
+ kernel,
26
+ prefix: '/api',
27
+ cors: {
28
+ origin: ['https://app.example.com', 'https://*.example.com'],
29
+ credentials: true,
30
+ },
31
+ });
17
32
 
18
- export default app;
33
+ export default app; // Cloudflare Workers / Bun / Deno / Vercel Edge
19
34
  ```
35
+
36
+ ### Embed into an existing Hono app
37
+
38
+ ```typescript
39
+ import { Hono } from 'hono';
40
+ import { objectStackMiddleware } from '@objectstack/hono';
41
+
42
+ const app = new Hono();
43
+ app.use('/api/*', objectStackMiddleware(kernel));
44
+ ```
45
+
46
+ ## Key Exports
47
+
48
+ | Export | Kind | Description |
49
+ |:---|:---|:---|
50
+ | `createHonoApp(options)` | function | New `Hono` instance with dispatcher + CORS installed. |
51
+ | `objectStackMiddleware(kernel)` | function | Reusable middleware for existing Hono apps. |
52
+ | `ObjectStackHonoOptions` | interface | Root options with `kernel`, `prefix`, `cors`. |
53
+ | `ObjectStackHonoCorsOptions` | interface | CORS configuration. |
54
+
55
+ ## Configuration
56
+
57
+ | Option | Type | Default | Notes |
58
+ |:---|:---|:---|:---|
59
+ | `kernel` | `ObjectKernel` | — | Bootstrapped kernel. |
60
+ | `prefix` | `string` | `'/api'` | Base path. |
61
+ | `cors` | `ObjectStackHonoCorsOptions \| false` | Enabled | Set to `false` to disable. |
62
+ | `cors.origin` | `string \| string[]` | env `CORS_ORIGIN` or `'*'` | Supports wildcard patterns. |
63
+ | `cors.credentials` | `boolean` | `false` | Required for cookie sessions. |
64
+ | `cors.exposeHeaders` | `string[]` | `['set-auth-token']` merged | Always includes `set-auth-token` for better-auth. |
65
+
66
+ ## Edge runtime notes
67
+
68
+ - Hono adapter is the **preferred** adapter for Cloudflare Workers, Deno Deploy, Bun, and Vercel Edge.
69
+ - Drivers differ by runtime: use [`@objectstack/driver-turso`](../../plugins/driver-turso) on edge; [`@objectstack/driver-sql`](../../plugins/driver-sql) on Node.
70
+ - Persist no long-lived state in module scope beyond the `kernel` instance.
71
+
72
+ ## When to use
73
+
74
+ - ✅ Edge, serverless, and multi-runtime deployments.
75
+ - ✅ Projects wanting built-in CORS with wildcard patterns.
76
+
77
+ ## When not to use
78
+
79
+ - ❌ Existing Express app — use [`@objectstack/express`](../express).
80
+ - ❌ NestJS enterprise stacks — use [`@objectstack/nestjs`](../nestjs).
81
+
82
+ ## Related Packages
83
+
84
+ - [`@objectstack/plugin-hono-server`](../../plugins/plugin-hono-server) — optional plugin that hosts a Hono server inside the kernel.
85
+ - [`@objectstack/runtime`](../../runtime), [`@objectstack/rest`](../../rest).
86
+
87
+ ## Links
88
+
89
+ - 📖 Docs: <https://objectstack.ai/docs>
90
+ - 📚 API Reference: <https://objectstack.ai/docs/references>
91
+
92
+ ## License
93
+
94
+ Apache-2.0 © ObjectStack
package/dist/index.d.mts CHANGED
@@ -1,6 +1,17 @@
1
1
  import { Hono } from 'hono';
2
2
  import { ObjectKernel } from '@objectstack/runtime';
3
3
 
4
+ /**
5
+ * Minimal structural interface matching KernelManager from @objectstack/service-cloud.
6
+ * Declared locally to avoid a circular build dependency.
7
+ */
8
+ type KernelManager = any;
9
+ /**
10
+ * Opaque reference to an EnvironmentDriverRegistry from @objectstack/service-cloud.
11
+ * Declared locally to avoid a circular build dependency. Pass an instance
12
+ * of DefaultEnvironmentDriverRegistry from @objectstack/service-cloud at runtime.
13
+ */
14
+ type EnvironmentDriverRegistry = any;
4
15
  interface ObjectStackHonoCorsOptions {
5
16
  /** Enable or disable CORS. Defaults to true. */
6
17
  enabled?: boolean;
@@ -22,6 +33,20 @@ interface ObjectStackHonoOptions {
22
33
  prefix?: string;
23
34
  /** CORS configuration. Set to `false` to disable entirely. */
24
35
  cors?: ObjectStackHonoCorsOptions | false;
36
+ /**
37
+ * Optional {@link KernelManager}. When provided, the dispatcher will route
38
+ * per-project requests to a project-scoped kernel resolved via
39
+ * `kernelManager.getOrCreate(projectId)`. When absent (self-hosted mode),
40
+ * all requests use the single `kernel` passed above.
41
+ */
42
+ kernelManager?: KernelManager;
43
+ /**
44
+ * Optional {@link EnvironmentDriverRegistry}. When provided, the dispatcher
45
+ * resolves incoming requests to a project via hostname / `X-Project-Id`
46
+ * header / session before invoking the KernelManager. Required for
47
+ * host-based routing in cloud / multi-project mode.
48
+ */
49
+ envRegistry?: EnvironmentDriverRegistry;
25
50
  }
26
51
  /**
27
52
  * Middleware mode for existing Hono apps
@@ -48,4 +73,4 @@ declare function objectStackMiddleware(kernel: ObjectKernel): (c: any, next: any
48
73
  */
49
74
  declare function createHonoApp(options: ObjectStackHonoOptions): Hono;
50
75
 
51
- export { type ObjectStackHonoCorsOptions, type ObjectStackHonoOptions, createHonoApp, objectStackMiddleware };
76
+ export { type EnvironmentDriverRegistry, type KernelManager, type ObjectStackHonoCorsOptions, type ObjectStackHonoOptions, createHonoApp, objectStackMiddleware };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,17 @@
1
1
  import { Hono } from 'hono';
2
2
  import { ObjectKernel } from '@objectstack/runtime';
3
3
 
4
+ /**
5
+ * Minimal structural interface matching KernelManager from @objectstack/service-cloud.
6
+ * Declared locally to avoid a circular build dependency.
7
+ */
8
+ type KernelManager = any;
9
+ /**
10
+ * Opaque reference to an EnvironmentDriverRegistry from @objectstack/service-cloud.
11
+ * Declared locally to avoid a circular build dependency. Pass an instance
12
+ * of DefaultEnvironmentDriverRegistry from @objectstack/service-cloud at runtime.
13
+ */
14
+ type EnvironmentDriverRegistry = any;
4
15
  interface ObjectStackHonoCorsOptions {
5
16
  /** Enable or disable CORS. Defaults to true. */
6
17
  enabled?: boolean;
@@ -22,6 +33,20 @@ interface ObjectStackHonoOptions {
22
33
  prefix?: string;
23
34
  /** CORS configuration. Set to `false` to disable entirely. */
24
35
  cors?: ObjectStackHonoCorsOptions | false;
36
+ /**
37
+ * Optional {@link KernelManager}. When provided, the dispatcher will route
38
+ * per-project requests to a project-scoped kernel resolved via
39
+ * `kernelManager.getOrCreate(projectId)`. When absent (self-hosted mode),
40
+ * all requests use the single `kernel` passed above.
41
+ */
42
+ kernelManager?: KernelManager;
43
+ /**
44
+ * Optional {@link EnvironmentDriverRegistry}. When provided, the dispatcher
45
+ * resolves incoming requests to a project via hostname / `X-Project-Id`
46
+ * header / session before invoking the KernelManager. Required for
47
+ * host-based routing in cloud / multi-project mode.
48
+ */
49
+ envRegistry?: EnvironmentDriverRegistry;
25
50
  }
26
51
  /**
27
52
  * Middleware mode for existing Hono apps
@@ -48,4 +73,4 @@ declare function objectStackMiddleware(kernel: ObjectKernel): (c: any, next: any
48
73
  */
49
74
  declare function createHonoApp(options: ObjectStackHonoOptions): Hono;
50
75
 
51
- export { type ObjectStackHonoCorsOptions, type ObjectStackHonoOptions, createHonoApp, objectStackMiddleware };
76
+ export { type EnvironmentDriverRegistry, type KernelManager, type ObjectStackHonoCorsOptions, type ObjectStackHonoOptions, createHonoApp, objectStackMiddleware };
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ module.exports = __toCommonJS(index_exports);
27
27
  var import_hono = require("hono");
28
28
  var import_cors = require("hono/cors");
29
29
  var import_runtime = require("@objectstack/runtime");
30
+ var import_plugin_hono_server = require("@objectstack/plugin-hono-server");
30
31
  function objectStackMiddleware(kernel) {
31
32
  return async (c, next) => {
32
33
  c.set("objectStack", kernel);
@@ -36,7 +37,11 @@ function objectStackMiddleware(kernel) {
36
37
  function createHonoApp(options) {
37
38
  const app = new import_hono.Hono();
38
39
  const prefix = options.prefix || "/api";
39
- const dispatcher = new import_runtime.HttpDispatcher(options.kernel);
40
+ const dispatcher = new import_runtime.HttpDispatcher(
41
+ options.kernel,
42
+ options.envRegistry,
43
+ options.kernelManager ? { kernelManager: options.kernelManager } : void 0
44
+ );
40
45
  const corsDisabledByEnv = process.env.CORS_ENABLED === "false";
41
46
  if (options.cors !== false && !corsDisabledByEnv) {
42
47
  const corsOpts = typeof options.cors === "object" ? options.cors : {};
@@ -54,16 +59,23 @@ function createHonoApp(options) {
54
59
  const credentials = corsOpts.credentials ?? process.env.CORS_CREDENTIALS !== "false";
55
60
  const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);
56
61
  let origin;
57
- if (credentials && configuredOrigin === "*") {
62
+ if (configuredOrigin === "*" && credentials) {
58
63
  origin = (requestOrigin) => requestOrigin || "*";
64
+ } else if ((0, import_plugin_hono_server.hasWildcardPattern)(configuredOrigin)) {
65
+ origin = (0, import_plugin_hono_server.createOriginMatcher)(configuredOrigin);
59
66
  } else {
60
67
  origin = configuredOrigin;
61
68
  }
69
+ const defaultExposeHeaders = ["set-auth-token"];
70
+ const exposeHeaders = Array.from(/* @__PURE__ */ new Set([
71
+ ...defaultExposeHeaders,
72
+ ...corsOpts.exposeHeaders ?? []
73
+ ]));
62
74
  app.use("*", (0, import_cors.cors)({
63
75
  origin,
64
76
  allowMethods: corsOpts.methods || ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
65
- allowHeaders: corsOpts.allowHeaders || ["Content-Type", "Authorization", "X-Requested-With"],
66
- exposeHeaders: corsOpts.exposeHeaders || [],
77
+ allowHeaders: corsOpts.allowHeaders || ["Content-Type", "Authorization", "X-Requested-With", "X-Tenant-ID", "X-Project-Id"],
78
+ exposeHeaders,
67
79
  credentials,
68
80
  maxAge
69
81
  }));
@@ -144,6 +156,26 @@ function createHonoApp(options) {
144
156
  } catch {
145
157
  authService = null;
146
158
  }
159
+ if (path === "config" && method === "GET" && authService) {
160
+ try {
161
+ const config = authService.getPublicConfig?.();
162
+ if (config) {
163
+ return c.json({
164
+ success: true,
165
+ data: config
166
+ });
167
+ }
168
+ } catch (error) {
169
+ const err = error instanceof Error ? error : new Error(String(error));
170
+ return c.json({
171
+ success: false,
172
+ error: {
173
+ code: "auth_config_error",
174
+ message: err.message
175
+ }
176
+ }, 500);
177
+ }
178
+ }
147
179
  if (authService && typeof authService.handleRequest === "function") {
148
180
  const response = await authService.handleRequest(c.req.raw);
149
181
  return new Response(response.body, {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';\n\nexport interface ObjectStackHonoCorsOptions {\n /** Enable or disable CORS. Defaults to true. */\n enabled?: boolean;\n /** Allowed origins. Defaults to env `CORS_ORIGIN` or '*'. Comma-separated string or array. */\n origin?: string | string[];\n /** Allowed methods. */\n methods?: string[];\n /** Allow credentials (cookies, authorization headers). */\n credentials?: boolean;\n /** Preflight cache max-age in seconds. */\n maxAge?: number;\n /** Allowed headers. */\n allowHeaders?: string[];\n /** Exposed headers. */\n exposeHeaders?: string[];\n}\n\nexport interface ObjectStackHonoOptions {\n kernel: ObjectKernel;\n prefix?: string;\n /** CORS configuration. Set to `false` to disable entirely. */\n cors?: ObjectStackHonoCorsOptions | false;\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 // ─── CORS Middleware ──────────────────────────────────────────────────────\n // Enabled by default. Controlled via options.cors or environment variables:\n // CORS_ENABLED – \"false\" to disable (default: true)\n // CORS_ORIGIN – comma-separated origins or \"*\" (default: \"*\")\n // CORS_CREDENTIALS – \"false\" to disallow credentials (default: true)\n // CORS_MAX_AGE – preflight cache seconds (default: 86400)\n const corsDisabledByEnv = process.env.CORS_ENABLED === 'false';\n if (options.cors !== false && !corsDisabledByEnv) {\n const corsOpts = typeof options.cors === 'object' ? options.cors : {};\n const enabled = corsOpts.enabled ?? true;\n\n if (enabled) {\n // Resolve origins: options > env > default '*'\n let configuredOrigin: string | string[];\n if (corsOpts.origin) {\n configuredOrigin = corsOpts.origin;\n } else if (process.env.CORS_ORIGIN) {\n const envOrigin = process.env.CORS_ORIGIN.trim();\n configuredOrigin = envOrigin.includes(',') ? envOrigin.split(',').map(s => s.trim()) : envOrigin;\n } else {\n configuredOrigin = '*';\n }\n\n const credentials = corsOpts.credentials ?? (process.env.CORS_CREDENTIALS !== 'false');\n const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);\n\n // When credentials is true, browsers reject wildcard '*' for Access-Control-Allow-Origin.\n // Use a function to reflect the request's Origin header instead.\n let origin: string | string[] | ((origin: string) => string | undefined | null);\n if (credentials && configuredOrigin === '*') {\n origin = (requestOrigin: string) => requestOrigin || '*';\n } else {\n origin = configuredOrigin;\n }\n\n app.use('*', cors({\n origin: origin as any,\n allowMethods: corsOpts.methods || ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],\n allowHeaders: corsOpts.allowHeaders || ['Content-Type', 'Authorization', 'X-Requested-With'],\n exposeHeaders: corsOpts.exposeHeaders || [],\n credentials,\n maxAge,\n }));\n }\n }\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.events) {\n // SSE / Vercel Data Stream streaming response\n const headers: Record<string, string> = {\n 'Content-Type': res.contentType || 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n ...(res.headers || {}),\n };\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const encoder = new TextEncoder();\n for await (const event of res.events) {\n const chunk = res.vercelDataStream\n ? (typeof event === 'string' ? event : JSON.stringify(event) + '\\n')\n : `data: ${JSON.stringify(event)}\\n\\n`;\n controller.enqueue(encoder.encode(chunk));\n }\n } catch (err) {\n // Stream error — close gracefully\n } finally {\n controller.close();\n }\n },\n });\n return new Response(stream, { status: 200, headers });\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 app.get(`${prefix}/discovery`, 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 }, prefix);\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 { cors } from 'hono/cors';\nimport {\n type ObjectKernel,\n HttpDispatcher,\n HttpDispatcherResult,\n} from '@objectstack/runtime';\n\n/**\n * Minimal structural interface matching KernelManager from @objectstack/service-cloud.\n * Declared locally to avoid a circular build dependency.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type KernelManager = any;\n\n/**\n * Opaque reference to an EnvironmentDriverRegistry from @objectstack/service-cloud.\n * Declared locally to avoid a circular build dependency. Pass an instance\n * of DefaultEnvironmentDriverRegistry from @objectstack/service-cloud at runtime.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type EnvironmentDriverRegistry = any;\nimport { createOriginMatcher, hasWildcardPattern } from '@objectstack/plugin-hono-server';\n\nexport interface ObjectStackHonoCorsOptions {\n /** Enable or disable CORS. Defaults to true. */\n enabled?: boolean;\n /** Allowed origins. Defaults to env `CORS_ORIGIN` or '*'. Comma-separated string or array. */\n origin?: string | string[];\n /** Allowed methods. */\n methods?: string[];\n /** Allow credentials (cookies, authorization headers). */\n credentials?: boolean;\n /** Preflight cache max-age in seconds. */\n maxAge?: number;\n /** Allowed headers. */\n allowHeaders?: string[];\n /** Exposed headers. */\n exposeHeaders?: string[];\n}\n\nexport interface ObjectStackHonoOptions {\n kernel: ObjectKernel;\n prefix?: string;\n /** CORS configuration. Set to `false` to disable entirely. */\n cors?: ObjectStackHonoCorsOptions | false;\n /**\n * Optional {@link KernelManager}. When provided, the dispatcher will route\n * per-project requests to a project-scoped kernel resolved via\n * `kernelManager.getOrCreate(projectId)`. When absent (self-hosted mode),\n * all requests use the single `kernel` passed above.\n */\n kernelManager?: KernelManager;\n /**\n * Optional {@link EnvironmentDriverRegistry}. When provided, the dispatcher\n * resolves incoming requests to a project via hostname / `X-Project-Id`\n * header / session before invoking the KernelManager. Required for\n * host-based routing in cloud / multi-project mode.\n */\n envRegistry?: EnvironmentDriverRegistry;\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(\n options.kernel,\n options.envRegistry,\n options.kernelManager ? { kernelManager: options.kernelManager } : undefined,\n );\n\n // ─── CORS Middleware ──────────────────────────────────────────────────────\n // Enabled by default. Controlled via options.cors or environment variables:\n // CORS_ENABLED – \"false\" to disable (default: true)\n // CORS_ORIGIN – comma-separated origins or \"*\" (default: \"*\")\n // CORS_CREDENTIALS – \"false\" to disallow credentials (default: true)\n // CORS_MAX_AGE – preflight cache seconds (default: 86400)\n const corsDisabledByEnv = process.env.CORS_ENABLED === 'false';\n if (options.cors !== false && !corsDisabledByEnv) {\n const corsOpts = typeof options.cors === 'object' ? options.cors : {};\n const enabled = corsOpts.enabled ?? true;\n\n if (enabled) {\n // Resolve origins: options > env > default '*'\n let configuredOrigin: string | string[];\n if (corsOpts.origin) {\n configuredOrigin = corsOpts.origin;\n } else if (process.env.CORS_ORIGIN) {\n const envOrigin = process.env.CORS_ORIGIN.trim();\n configuredOrigin = envOrigin.includes(',') ? envOrigin.split(',').map(s => s.trim()) : envOrigin;\n } else {\n configuredOrigin = '*';\n }\n\n const credentials = corsOpts.credentials ?? (process.env.CORS_CREDENTIALS !== 'false');\n const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);\n\n // When credentials is true, browsers reject wildcard '*' for Access-Control-Allow-Origin.\n // For wildcard patterns (like \"https://*.example.com\" or \"http://localhost:*\") we must\n // use a matcher function — Hono's cors() middleware does exact-string matching only and\n // treats '*' in patterns as a literal character, so passing wildcard strings straight\n // through would silently drop the Access-Control-Allow-Origin header on every real\n // request (preflight can still succeed via apps/objectos's short-circuit, but the\n // subsequent POST/GET would be blocked by the browser).\n //\n // This mirrors `plugin-hono-server`'s CORS wiring and uses the shared pattern matcher\n // from `@objectstack/plugin-hono-server` so all Hono-based code paths stay in sync.\n let origin: string | string[] | ((origin: string) => string | undefined | null);\n if (configuredOrigin === '*' && credentials) {\n // Credentials mode with '*' — reflect the request origin\n origin = (requestOrigin: string) => requestOrigin || '*';\n } else if (hasWildcardPattern(configuredOrigin)) {\n // Wildcard patterns (e.g., \"https://*.objectui.org\", \"http://localhost:*\")\n origin = createOriginMatcher(configuredOrigin);\n } else {\n // Exact origin(s) — pass through as-is\n origin = configuredOrigin;\n }\n\n // Always include `set-auth-token` in exposed headers so that the\n // better-auth `bearer()` plugin (registered by plugin-auth) can\n // deliver rotated session tokens to cross-origin clients. Without\n // this, browsers strip the header from every response, the client\n // never sees the new token, and cross-origin sessions silently\n // break even when preflight and the actual request both succeed.\n //\n // This mirrors `plugin-hono-server`'s CORS wiring — all three\n // Hono-based CORS sites must stay in lockstep on this default.\n const defaultExposeHeaders = ['set-auth-token'];\n const exposeHeaders = Array.from(new Set([\n ...defaultExposeHeaders,\n ...(corsOpts.exposeHeaders ?? []),\n ]));\n\n app.use('*', cors({\n origin: origin as any,\n allowMethods: corsOpts.methods || ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],\n allowHeaders: corsOpts.allowHeaders || ['Content-Type', 'Authorization', 'X-Requested-With', 'X-Tenant-ID', 'X-Project-Id'],\n exposeHeaders,\n credentials,\n maxAge,\n }));\n }\n }\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.events) {\n // SSE / Vercel Data Stream streaming response\n const headers: Record<string, string> = {\n 'Content-Type': res.contentType || 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n ...(res.headers || {}),\n };\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const encoder = new TextEncoder();\n for await (const event of res.events) {\n const chunk = res.vercelDataStream\n ? (typeof event === 'string' ? event : JSON.stringify(event) + '\\n')\n : `data: ${JSON.stringify(event)}\\n\\n`;\n controller.enqueue(encoder.encode(chunk));\n }\n } catch (err) {\n // Stream error — close gracefully\n } finally {\n controller.close();\n }\n },\n });\n return new Response(stream, { status: 200, headers });\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 app.get(`${prefix}/discovery`, 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 // Handle /auth/config endpoint specifically (not handled by better-auth)\n if (path === 'config' && method === 'GET' && authService) {\n try {\n const config = (authService as any).getPublicConfig?.();\n if (config) {\n return c.json({\n success: true,\n data: config,\n });\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return c.json({\n success: false,\n error: {\n code: 'auth_config_error',\n message: err.message,\n },\n }, 500);\n }\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 }, prefix);\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,7 +1,10 @@
1
1
  // src/index.ts
2
2
  import { Hono } from "hono";
3
3
  import { cors } from "hono/cors";
4
- import { HttpDispatcher } from "@objectstack/runtime";
4
+ import {
5
+ HttpDispatcher
6
+ } from "@objectstack/runtime";
7
+ import { createOriginMatcher, hasWildcardPattern } from "@objectstack/plugin-hono-server";
5
8
  function objectStackMiddleware(kernel) {
6
9
  return async (c, next) => {
7
10
  c.set("objectStack", kernel);
@@ -11,7 +14,11 @@ function objectStackMiddleware(kernel) {
11
14
  function createHonoApp(options) {
12
15
  const app = new Hono();
13
16
  const prefix = options.prefix || "/api";
14
- const dispatcher = new HttpDispatcher(options.kernel);
17
+ const dispatcher = new HttpDispatcher(
18
+ options.kernel,
19
+ options.envRegistry,
20
+ options.kernelManager ? { kernelManager: options.kernelManager } : void 0
21
+ );
15
22
  const corsDisabledByEnv = process.env.CORS_ENABLED === "false";
16
23
  if (options.cors !== false && !corsDisabledByEnv) {
17
24
  const corsOpts = typeof options.cors === "object" ? options.cors : {};
@@ -29,16 +36,23 @@ function createHonoApp(options) {
29
36
  const credentials = corsOpts.credentials ?? process.env.CORS_CREDENTIALS !== "false";
30
37
  const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);
31
38
  let origin;
32
- if (credentials && configuredOrigin === "*") {
39
+ if (configuredOrigin === "*" && credentials) {
33
40
  origin = (requestOrigin) => requestOrigin || "*";
41
+ } else if (hasWildcardPattern(configuredOrigin)) {
42
+ origin = createOriginMatcher(configuredOrigin);
34
43
  } else {
35
44
  origin = configuredOrigin;
36
45
  }
46
+ const defaultExposeHeaders = ["set-auth-token"];
47
+ const exposeHeaders = Array.from(/* @__PURE__ */ new Set([
48
+ ...defaultExposeHeaders,
49
+ ...corsOpts.exposeHeaders ?? []
50
+ ]));
37
51
  app.use("*", cors({
38
52
  origin,
39
53
  allowMethods: corsOpts.methods || ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
40
- allowHeaders: corsOpts.allowHeaders || ["Content-Type", "Authorization", "X-Requested-With"],
41
- exposeHeaders: corsOpts.exposeHeaders || [],
54
+ allowHeaders: corsOpts.allowHeaders || ["Content-Type", "Authorization", "X-Requested-With", "X-Tenant-ID", "X-Project-Id"],
55
+ exposeHeaders,
42
56
  credentials,
43
57
  maxAge
44
58
  }));
@@ -119,6 +133,26 @@ function createHonoApp(options) {
119
133
  } catch {
120
134
  authService = null;
121
135
  }
136
+ if (path === "config" && method === "GET" && authService) {
137
+ try {
138
+ const config = authService.getPublicConfig?.();
139
+ if (config) {
140
+ return c.json({
141
+ success: true,
142
+ data: config
143
+ });
144
+ }
145
+ } catch (error) {
146
+ const err = error instanceof Error ? error : new Error(String(error));
147
+ return c.json({
148
+ success: false,
149
+ error: {
150
+ code: "auth_config_error",
151
+ message: err.message
152
+ }
153
+ }, 500);
154
+ }
155
+ }
122
156
  if (authService && typeof authService.handleRequest === "function") {
123
157
  const response = await authService.handleRequest(c.req.raw);
124
158
  return new Response(response.body, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';\n\nexport interface ObjectStackHonoCorsOptions {\n /** Enable or disable CORS. Defaults to true. */\n enabled?: boolean;\n /** Allowed origins. Defaults to env `CORS_ORIGIN` or '*'. Comma-separated string or array. */\n origin?: string | string[];\n /** Allowed methods. */\n methods?: string[];\n /** Allow credentials (cookies, authorization headers). */\n credentials?: boolean;\n /** Preflight cache max-age in seconds. */\n maxAge?: number;\n /** Allowed headers. */\n allowHeaders?: string[];\n /** Exposed headers. */\n exposeHeaders?: string[];\n}\n\nexport interface ObjectStackHonoOptions {\n kernel: ObjectKernel;\n prefix?: string;\n /** CORS configuration. Set to `false` to disable entirely. */\n cors?: ObjectStackHonoCorsOptions | false;\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 // ─── CORS Middleware ──────────────────────────────────────────────────────\n // Enabled by default. Controlled via options.cors or environment variables:\n // CORS_ENABLED – \"false\" to disable (default: true)\n // CORS_ORIGIN – comma-separated origins or \"*\" (default: \"*\")\n // CORS_CREDENTIALS – \"false\" to disallow credentials (default: true)\n // CORS_MAX_AGE – preflight cache seconds (default: 86400)\n const corsDisabledByEnv = process.env.CORS_ENABLED === 'false';\n if (options.cors !== false && !corsDisabledByEnv) {\n const corsOpts = typeof options.cors === 'object' ? options.cors : {};\n const enabled = corsOpts.enabled ?? true;\n\n if (enabled) {\n // Resolve origins: options > env > default '*'\n let configuredOrigin: string | string[];\n if (corsOpts.origin) {\n configuredOrigin = corsOpts.origin;\n } else if (process.env.CORS_ORIGIN) {\n const envOrigin = process.env.CORS_ORIGIN.trim();\n configuredOrigin = envOrigin.includes(',') ? envOrigin.split(',').map(s => s.trim()) : envOrigin;\n } else {\n configuredOrigin = '*';\n }\n\n const credentials = corsOpts.credentials ?? (process.env.CORS_CREDENTIALS !== 'false');\n const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);\n\n // When credentials is true, browsers reject wildcard '*' for Access-Control-Allow-Origin.\n // Use a function to reflect the request's Origin header instead.\n let origin: string | string[] | ((origin: string) => string | undefined | null);\n if (credentials && configuredOrigin === '*') {\n origin = (requestOrigin: string) => requestOrigin || '*';\n } else {\n origin = configuredOrigin;\n }\n\n app.use('*', cors({\n origin: origin as any,\n allowMethods: corsOpts.methods || ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],\n allowHeaders: corsOpts.allowHeaders || ['Content-Type', 'Authorization', 'X-Requested-With'],\n exposeHeaders: corsOpts.exposeHeaders || [],\n credentials,\n maxAge,\n }));\n }\n }\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.events) {\n // SSE / Vercel Data Stream streaming response\n const headers: Record<string, string> = {\n 'Content-Type': res.contentType || 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n ...(res.headers || {}),\n };\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const encoder = new TextEncoder();\n for await (const event of res.events) {\n const chunk = res.vercelDataStream\n ? (typeof event === 'string' ? event : JSON.stringify(event) + '\\n')\n : `data: ${JSON.stringify(event)}\\n\\n`;\n controller.enqueue(encoder.encode(chunk));\n }\n } catch (err) {\n // Stream error — close gracefully\n } finally {\n controller.close();\n }\n },\n });\n return new Response(stream, { status: 200, headers });\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 app.get(`${prefix}/discovery`, 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 }, prefix);\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 { cors } from 'hono/cors';\nimport {\n type ObjectKernel,\n HttpDispatcher,\n HttpDispatcherResult,\n} from '@objectstack/runtime';\n\n/**\n * Minimal structural interface matching KernelManager from @objectstack/service-cloud.\n * Declared locally to avoid a circular build dependency.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type KernelManager = any;\n\n/**\n * Opaque reference to an EnvironmentDriverRegistry from @objectstack/service-cloud.\n * Declared locally to avoid a circular build dependency. Pass an instance\n * of DefaultEnvironmentDriverRegistry from @objectstack/service-cloud at runtime.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type EnvironmentDriverRegistry = any;\nimport { createOriginMatcher, hasWildcardPattern } from '@objectstack/plugin-hono-server';\n\nexport interface ObjectStackHonoCorsOptions {\n /** Enable or disable CORS. Defaults to true. */\n enabled?: boolean;\n /** Allowed origins. Defaults to env `CORS_ORIGIN` or '*'. Comma-separated string or array. */\n origin?: string | string[];\n /** Allowed methods. */\n methods?: string[];\n /** Allow credentials (cookies, authorization headers). */\n credentials?: boolean;\n /** Preflight cache max-age in seconds. */\n maxAge?: number;\n /** Allowed headers. */\n allowHeaders?: string[];\n /** Exposed headers. */\n exposeHeaders?: string[];\n}\n\nexport interface ObjectStackHonoOptions {\n kernel: ObjectKernel;\n prefix?: string;\n /** CORS configuration. Set to `false` to disable entirely. */\n cors?: ObjectStackHonoCorsOptions | false;\n /**\n * Optional {@link KernelManager}. When provided, the dispatcher will route\n * per-project requests to a project-scoped kernel resolved via\n * `kernelManager.getOrCreate(projectId)`. When absent (self-hosted mode),\n * all requests use the single `kernel` passed above.\n */\n kernelManager?: KernelManager;\n /**\n * Optional {@link EnvironmentDriverRegistry}. When provided, the dispatcher\n * resolves incoming requests to a project via hostname / `X-Project-Id`\n * header / session before invoking the KernelManager. Required for\n * host-based routing in cloud / multi-project mode.\n */\n envRegistry?: EnvironmentDriverRegistry;\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(\n options.kernel,\n options.envRegistry,\n options.kernelManager ? { kernelManager: options.kernelManager } : undefined,\n );\n\n // ─── CORS Middleware ──────────────────────────────────────────────────────\n // Enabled by default. Controlled via options.cors or environment variables:\n // CORS_ENABLED – \"false\" to disable (default: true)\n // CORS_ORIGIN – comma-separated origins or \"*\" (default: \"*\")\n // CORS_CREDENTIALS – \"false\" to disallow credentials (default: true)\n // CORS_MAX_AGE – preflight cache seconds (default: 86400)\n const corsDisabledByEnv = process.env.CORS_ENABLED === 'false';\n if (options.cors !== false && !corsDisabledByEnv) {\n const corsOpts = typeof options.cors === 'object' ? options.cors : {};\n const enabled = corsOpts.enabled ?? true;\n\n if (enabled) {\n // Resolve origins: options > env > default '*'\n let configuredOrigin: string | string[];\n if (corsOpts.origin) {\n configuredOrigin = corsOpts.origin;\n } else if (process.env.CORS_ORIGIN) {\n const envOrigin = process.env.CORS_ORIGIN.trim();\n configuredOrigin = envOrigin.includes(',') ? envOrigin.split(',').map(s => s.trim()) : envOrigin;\n } else {\n configuredOrigin = '*';\n }\n\n const credentials = corsOpts.credentials ?? (process.env.CORS_CREDENTIALS !== 'false');\n const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);\n\n // When credentials is true, browsers reject wildcard '*' for Access-Control-Allow-Origin.\n // For wildcard patterns (like \"https://*.example.com\" or \"http://localhost:*\") we must\n // use a matcher function — Hono's cors() middleware does exact-string matching only and\n // treats '*' in patterns as a literal character, so passing wildcard strings straight\n // through would silently drop the Access-Control-Allow-Origin header on every real\n // request (preflight can still succeed via apps/objectos's short-circuit, but the\n // subsequent POST/GET would be blocked by the browser).\n //\n // This mirrors `plugin-hono-server`'s CORS wiring and uses the shared pattern matcher\n // from `@objectstack/plugin-hono-server` so all Hono-based code paths stay in sync.\n let origin: string | string[] | ((origin: string) => string | undefined | null);\n if (configuredOrigin === '*' && credentials) {\n // Credentials mode with '*' — reflect the request origin\n origin = (requestOrigin: string) => requestOrigin || '*';\n } else if (hasWildcardPattern(configuredOrigin)) {\n // Wildcard patterns (e.g., \"https://*.objectui.org\", \"http://localhost:*\")\n origin = createOriginMatcher(configuredOrigin);\n } else {\n // Exact origin(s) — pass through as-is\n origin = configuredOrigin;\n }\n\n // Always include `set-auth-token` in exposed headers so that the\n // better-auth `bearer()` plugin (registered by plugin-auth) can\n // deliver rotated session tokens to cross-origin clients. Without\n // this, browsers strip the header from every response, the client\n // never sees the new token, and cross-origin sessions silently\n // break even when preflight and the actual request both succeed.\n //\n // This mirrors `plugin-hono-server`'s CORS wiring — all three\n // Hono-based CORS sites must stay in lockstep on this default.\n const defaultExposeHeaders = ['set-auth-token'];\n const exposeHeaders = Array.from(new Set([\n ...defaultExposeHeaders,\n ...(corsOpts.exposeHeaders ?? []),\n ]));\n\n app.use('*', cors({\n origin: origin as any,\n allowMethods: corsOpts.methods || ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],\n allowHeaders: corsOpts.allowHeaders || ['Content-Type', 'Authorization', 'X-Requested-With', 'X-Tenant-ID', 'X-Project-Id'],\n exposeHeaders,\n credentials,\n maxAge,\n }));\n }\n }\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.events) {\n // SSE / Vercel Data Stream streaming response\n const headers: Record<string, string> = {\n 'Content-Type': res.contentType || 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n ...(res.headers || {}),\n };\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const encoder = new TextEncoder();\n for await (const event of res.events) {\n const chunk = res.vercelDataStream\n ? (typeof event === 'string' ? event : JSON.stringify(event) + '\\n')\n : `data: ${JSON.stringify(event)}\\n\\n`;\n controller.enqueue(encoder.encode(chunk));\n }\n } catch (err) {\n // Stream error — close gracefully\n } finally {\n controller.close();\n }\n },\n });\n return new Response(stream, { status: 200, headers });\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 app.get(`${prefix}/discovery`, 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 // Handle /auth/config endpoint specifically (not handled by better-auth)\n if (path === 'config' && method === 'GET' && authService) {\n try {\n const config = (authService as any).getPublicConfig?.();\n if (config) {\n return c.json({\n success: true,\n data: config,\n });\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return c.json({\n success: false,\n error: {\n code: 'auth_config_error',\n message: err.message,\n },\n }, 500);\n }\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 }, prefix);\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": "4.0.4",
3
+ "version": "4.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,15 +11,44 @@
11
11
  "require": "./dist/index.js"
12
12
  }
13
13
  },
14
+ "dependencies": {
15
+ "@objectstack/plugin-hono-server": "4.1.0"
16
+ },
14
17
  "peerDependencies": {
15
18
  "hono": "^4.12.8",
16
- "@objectstack/runtime": "^4.0.4"
19
+ "@objectstack/runtime": "^4.1.0"
17
20
  },
18
21
  "devDependencies": {
19
- "hono": "^4.12.12",
20
- "typescript": "^6.0.2",
21
- "vitest": "^4.1.4",
22
- "@objectstack/runtime": "4.0.4"
22
+ "hono": "^4.12.21",
23
+ "typescript": "^6.0.3",
24
+ "vitest": "^4.1.7",
25
+ "@objectstack/runtime": "4.1.0"
26
+ },
27
+ "description": "Hono adapter for ObjectStack — edge-compatible REST API server for Cloudflare Workers, Deno, Bun, and Node.",
28
+ "keywords": [
29
+ "objectstack",
30
+ "hono",
31
+ "adapter",
32
+ "edge",
33
+ "rest"
34
+ ],
35
+ "author": "ObjectStack",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/objectstack-ai/framework.git",
39
+ "directory": "packages/adapters/hono"
40
+ },
41
+ "homepage": "https://objectstack.ai/docs",
42
+ "bugs": "https://github.com/objectstack-ai/framework/issues",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "README.md"
49
+ ],
50
+ "engines": {
51
+ "node": ">=18.0.0"
23
52
  },
24
53
  "scripts": {
25
54
  "build": "tsup --config ../../../tsup.config.ts",
@@ -1,22 +0,0 @@
1
-
2
- > @objectstack/hono@4.0.4 build /home/runner/work/framework/framework/packages/adapters/hono
3
- > tsup --config ../../../tsup.config.ts
4
-
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
9
- CLI Target: es2020
10
- CLI Cleaning output folder
11
- ESM Build start
12
- CJS Build start
13
- CJS dist/index.js 7.85 KB
14
- CJS dist/index.js.map 15.58 KB
15
- CJS ⚡️ Build success in 54ms
16
- ESM dist/index.mjs 6.75 KB
17
- ESM dist/index.mjs.map 15.55 KB
18
- ESM ⚡️ Build success in 54ms
19
- DTS Build start
20
- DTS ⚡️ Build success in 13070ms
21
- DTS dist/index.d.mts 1.87 KB
22
- DTS dist/index.d.ts 1.87 KB