@objectstack/hono 7.2.0 → 7.2.1

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/dist/index.d.mts CHANGED
@@ -15,7 +15,7 @@ type EnvironmentDriverRegistry = any;
15
15
  interface ObjectStackHonoCorsOptions {
16
16
  /** Enable or disable CORS. Defaults to true. */
17
17
  enabled?: boolean;
18
- /** Allowed origins. Defaults to env `CORS_ORIGIN` or '*'. Comma-separated string or array. */
18
+ /** Allowed origins. Defaults to env `OS_CORS_ORIGIN` (or legacy `CORS_ORIGIN`) or '*'. Comma-separated string or array. */
19
19
  origin?: string | string[];
20
20
  /** Allowed methods. */
21
21
  methods?: string[];
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ type EnvironmentDriverRegistry = any;
15
15
  interface ObjectStackHonoCorsOptions {
16
16
  /** Enable or disable CORS. Defaults to true. */
17
17
  enabled?: boolean;
18
- /** Allowed origins. Defaults to env `CORS_ORIGIN` or '*'. Comma-separated string or array. */
18
+ /** Allowed origins. Defaults to env `OS_CORS_ORIGIN` (or legacy `CORS_ORIGIN`) or '*'. Comma-separated string or array. */
19
19
  origin?: string | string[];
20
20
  /** Allowed methods. */
21
21
  methods?: string[];
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_types = require("@objectstack/types");
30
31
  var import_plugin_hono_server = require("@objectstack/plugin-hono-server");
31
32
  function objectStackMiddleware(kernel) {
32
33
  return async (c, next) => {
@@ -42,22 +43,24 @@ function createHonoApp(options) {
42
43
  options.envRegistry,
43
44
  options.kernelManager ? { kernelManager: options.kernelManager } : void 0
44
45
  );
45
- const corsDisabledByEnv = process.env.CORS_ENABLED === "false";
46
+ const corsDisabledByEnv = (0, import_types.readEnvWithDeprecation)("OS_CORS_ENABLED", "CORS_ENABLED") === "false";
46
47
  if (options.cors !== false && !corsDisabledByEnv) {
47
48
  const corsOpts = typeof options.cors === "object" ? options.cors : {};
48
49
  const enabled = corsOpts.enabled ?? true;
49
50
  if (enabled) {
50
51
  let configuredOrigin;
52
+ const corsOriginEnv = (0, import_types.readEnvWithDeprecation)("OS_CORS_ORIGIN", "CORS_ORIGIN");
51
53
  if (corsOpts.origin) {
52
54
  configuredOrigin = corsOpts.origin;
53
- } else if (process.env.CORS_ORIGIN) {
54
- const envOrigin = process.env.CORS_ORIGIN.trim();
55
+ } else if (corsOriginEnv) {
56
+ const envOrigin = corsOriginEnv.trim();
55
57
  configuredOrigin = envOrigin.includes(",") ? envOrigin.split(",").map((s) => s.trim()) : envOrigin;
56
58
  } else {
57
59
  configuredOrigin = "*";
58
60
  }
59
- const credentials = corsOpts.credentials ?? process.env.CORS_CREDENTIALS !== "false";
60
- const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);
61
+ const credentials = corsOpts.credentials ?? (0, import_types.readEnvWithDeprecation)("OS_CORS_CREDENTIALS", "CORS_CREDENTIALS") !== "false";
62
+ const maxAgeEnv = (0, import_types.readEnvWithDeprecation)("OS_CORS_MAX_AGE", "CORS_MAX_AGE");
63
+ const maxAge = corsOpts.maxAge ?? (maxAgeEnv ? parseInt(maxAgeEnv, 10) : 86400);
61
64
  let origin;
62
65
  if (configuredOrigin === "*" && credentials) {
63
66
  origin = (requestOrigin) => requestOrigin || "*";
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 {\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(environmentId)`. 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-Environment-Id`\n * header / session before invoking the KernelManager. Required for\n * host-based routing in cloud / multi-environment 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-Environment-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":[]}
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';\nimport { readEnvWithDeprecation } from '@objectstack/types';\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 `OS_CORS_ORIGIN` (or legacy `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(environmentId)`. 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-Environment-Id`\n * header / session before invoking the KernelManager. Required for\n * host-based routing in cloud / multi-environment 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 // OS_CORS_ENABLED – \"false\" to disable (default: true)\n // OS_CORS_ORIGIN – comma-separated origins or \"*\" (default: \"*\")\n // OS_CORS_CREDENTIALS – \"false\" to disallow credentials (default: true)\n // OS_CORS_MAX_AGE – preflight cache seconds (default: 86400)\n // (legacy CORS_* names still honoured with a deprecation warning)\n const corsDisabledByEnv = readEnvWithDeprecation('OS_CORS_ENABLED', '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 const corsOriginEnv = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN');\n if (corsOpts.origin) {\n configuredOrigin = corsOpts.origin;\n } else if (corsOriginEnv) {\n const envOrigin = corsOriginEnv.trim();\n configuredOrigin = envOrigin.includes(',') ? envOrigin.split(',').map(s => s.trim()) : envOrigin;\n } else {\n configuredOrigin = '*';\n }\n\n const credentials = corsOpts.credentials ?? (readEnvWithDeprecation('OS_CORS_CREDENTIALS', 'CORS_CREDENTIALS') !== 'false');\n const maxAgeEnv = readEnvWithDeprecation('OS_CORS_MAX_AGE', 'CORS_MAX_AGE');\n const maxAge = corsOpts.maxAge ?? (maxAgeEnv ? parseInt(maxAgeEnv, 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-Environment-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
@@ -4,6 +4,7 @@ import { cors } from "hono/cors";
4
4
  import {
5
5
  HttpDispatcher
6
6
  } from "@objectstack/runtime";
7
+ import { readEnvWithDeprecation } from "@objectstack/types";
7
8
  import { createOriginMatcher, hasWildcardPattern } from "@objectstack/plugin-hono-server";
8
9
  function objectStackMiddleware(kernel) {
9
10
  return async (c, next) => {
@@ -19,22 +20,24 @@ function createHonoApp(options) {
19
20
  options.envRegistry,
20
21
  options.kernelManager ? { kernelManager: options.kernelManager } : void 0
21
22
  );
22
- const corsDisabledByEnv = process.env.CORS_ENABLED === "false";
23
+ const corsDisabledByEnv = readEnvWithDeprecation("OS_CORS_ENABLED", "CORS_ENABLED") === "false";
23
24
  if (options.cors !== false && !corsDisabledByEnv) {
24
25
  const corsOpts = typeof options.cors === "object" ? options.cors : {};
25
26
  const enabled = corsOpts.enabled ?? true;
26
27
  if (enabled) {
27
28
  let configuredOrigin;
29
+ const corsOriginEnv = readEnvWithDeprecation("OS_CORS_ORIGIN", "CORS_ORIGIN");
28
30
  if (corsOpts.origin) {
29
31
  configuredOrigin = corsOpts.origin;
30
- } else if (process.env.CORS_ORIGIN) {
31
- const envOrigin = process.env.CORS_ORIGIN.trim();
32
+ } else if (corsOriginEnv) {
33
+ const envOrigin = corsOriginEnv.trim();
32
34
  configuredOrigin = envOrigin.includes(",") ? envOrigin.split(",").map((s) => s.trim()) : envOrigin;
33
35
  } else {
34
36
  configuredOrigin = "*";
35
37
  }
36
- const credentials = corsOpts.credentials ?? process.env.CORS_CREDENTIALS !== "false";
37
- const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);
38
+ const credentials = corsOpts.credentials ?? readEnvWithDeprecation("OS_CORS_CREDENTIALS", "CORS_CREDENTIALS") !== "false";
39
+ const maxAgeEnv = readEnvWithDeprecation("OS_CORS_MAX_AGE", "CORS_MAX_AGE");
40
+ const maxAge = corsOpts.maxAge ?? (maxAgeEnv ? parseInt(maxAgeEnv, 10) : 86400);
38
41
  let origin;
39
42
  if (configuredOrigin === "*" && credentials) {
40
43
  origin = (requestOrigin) => requestOrigin || "*";
@@ -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 {\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(environmentId)`. 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-Environment-Id`\n * header / session before invoking the KernelManager. Required for\n * host-based routing in cloud / multi-environment 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-Environment-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":[]}
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';\nimport { readEnvWithDeprecation } from '@objectstack/types';\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 `OS_CORS_ORIGIN` (or legacy `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(environmentId)`. 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-Environment-Id`\n * header / session before invoking the KernelManager. Required for\n * host-based routing in cloud / multi-environment 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 // OS_CORS_ENABLED – \"false\" to disable (default: true)\n // OS_CORS_ORIGIN – comma-separated origins or \"*\" (default: \"*\")\n // OS_CORS_CREDENTIALS – \"false\" to disallow credentials (default: true)\n // OS_CORS_MAX_AGE – preflight cache seconds (default: 86400)\n // (legacy CORS_* names still honoured with a deprecation warning)\n const corsDisabledByEnv = readEnvWithDeprecation('OS_CORS_ENABLED', '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 const corsOriginEnv = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN');\n if (corsOpts.origin) {\n configuredOrigin = corsOpts.origin;\n } else if (corsOriginEnv) {\n const envOrigin = corsOriginEnv.trim();\n configuredOrigin = envOrigin.includes(',') ? envOrigin.split(',').map(s => s.trim()) : envOrigin;\n } else {\n configuredOrigin = '*';\n }\n\n const credentials = corsOpts.credentials ?? (readEnvWithDeprecation('OS_CORS_CREDENTIALS', 'CORS_CREDENTIALS') !== 'false');\n const maxAgeEnv = readEnvWithDeprecation('OS_CORS_MAX_AGE', 'CORS_MAX_AGE');\n const maxAge = corsOpts.maxAge ?? (maxAgeEnv ? parseInt(maxAgeEnv, 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-Environment-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": "7.2.0",
3
+ "version": "7.2.1",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,17 +12,18 @@
12
12
  }
13
13
  },
14
14
  "dependencies": {
15
- "@objectstack/plugin-hono-server": "7.2.0"
15
+ "@objectstack/plugin-hono-server": "7.2.1",
16
+ "@objectstack/types": "7.2.1"
16
17
  },
17
18
  "peerDependencies": {
18
19
  "hono": "^4.12.8",
19
- "@objectstack/runtime": "^7.2.0"
20
+ "@objectstack/runtime": "^7.2.1"
20
21
  },
21
22
  "devDependencies": {
22
23
  "hono": "^4.12.23",
23
24
  "typescript": "^6.0.3",
24
25
  "vitest": "^4.1.7",
25
- "@objectstack/runtime": "7.2.0"
26
+ "@objectstack/runtime": "7.2.1"
26
27
  },
27
28
  "description": "Hono adapter for ObjectStack — edge-compatible REST API server for Cloudflare Workers, Deno, Bun, and Node.",
28
29
  "keywords": [