@objectstack/plugin-hono-server 4.0.3 → 4.0.5

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
@@ -8,6 +8,7 @@ HTTP Server adapter for ObjectStack using Hono.
8
8
  - **Fast**: Built on Hono, a high-performance web framework.
9
9
  - **Full Protocol Support**: Automatically provides all ObjectStack Runtime endpoints (Auth, Data, Metadata, etc.).
10
10
  - **Middleware**: Supports standard Hono middleware.
11
+ - **Wildcard CORS**: Supports wildcard patterns in CORS origins (compatible with better-auth).
11
12
 
12
13
  ## Usage
13
14
 
@@ -18,7 +19,7 @@ import { HonoServerPlugin } from '@objectstack/plugin-hono-server';
18
19
  const kernel = new ObjectKernel();
19
20
 
20
21
  // Register the server plugin
21
- kernel.use(new HonoServerPlugin({
22
+ kernel.use(new HonoServerPlugin({
22
23
  port: 3000,
23
24
  restConfig: {
24
25
  api: {
@@ -30,6 +31,97 @@ kernel.use(new HonoServerPlugin({
30
31
  await kernel.start();
31
32
  ```
32
33
 
34
+ ## CORS Configuration
35
+
36
+ The Hono server plugin supports flexible CORS configuration with wildcard pattern matching.
37
+
38
+ ### Basic CORS
39
+
40
+ ```typescript
41
+ kernel.use(new HonoServerPlugin({
42
+ port: 3000,
43
+ cors: {
44
+ origins: ['https://app.example.com'],
45
+ credentials: true
46
+ }
47
+ }));
48
+ ```
49
+
50
+ ### Wildcard Patterns (better-auth compatible)
51
+
52
+ ```typescript
53
+ // Subdomain wildcards
54
+ kernel.use(new HonoServerPlugin({
55
+ cors: {
56
+ origins: ['https://*.objectui.org', 'https://*.objectstack.ai'],
57
+ credentials: true
58
+ }
59
+ }));
60
+
61
+ // Port wildcards (useful for development)
62
+ kernel.use(new HonoServerPlugin({
63
+ cors: {
64
+ origins: 'http://localhost:*'
65
+ }
66
+ }));
67
+
68
+ // Comma-separated patterns
69
+ kernel.use(new HonoServerPlugin({
70
+ cors: {
71
+ origins: 'https://*.objectui.org,https://*.objectstack.ai,http://localhost:*'
72
+ }
73
+ }));
74
+ ```
75
+
76
+ ### Environment Variables
77
+
78
+ CORS can also be configured via environment variables:
79
+
80
+ ```bash
81
+ # Single origin
82
+ CORS_ORIGIN=https://app.example.com
83
+
84
+ # Wildcard patterns (comma-separated)
85
+ CORS_ORIGIN=https://*.objectui.org,https://*.objectstack.ai
86
+
87
+ # Disable CORS
88
+ CORS_ENABLED=false
89
+
90
+ # Additional options
91
+ CORS_CREDENTIALS=true
92
+ CORS_MAX_AGE=86400
93
+ ```
94
+
95
+ ### Disable CORS
96
+
97
+ ```typescript
98
+ kernel.use(new HonoServerPlugin({
99
+ cors: false // Completely disable CORS
100
+ }));
101
+ ```
102
+
103
+ ### Bearer token auth (`set-auth-token`)
104
+
105
+ The CORS middleware always includes `set-auth-token` in `Access-Control-Expose-Headers`
106
+ so that `@objectstack/plugin-auth`'s `bearer()` plugin can deliver rotated session
107
+ tokens to cross-origin and mobile clients. `Authorization` is included in
108
+ `Access-Control-Allow-Headers` by default so `Authorization: Bearer <token>`
109
+ requests succeed preflight.
110
+
111
+ You can contribute additional exposed / allowed headers — `exposeHeaders`
112
+ entries you supply are **merged** with the `set-auth-token` default:
113
+
114
+ ```typescript
115
+ kernel.use(new HonoServerPlugin({
116
+ cors: {
117
+ origins: ['https://app.example.com'],
118
+ credentials: true,
119
+ exposeHeaders: ['X-Request-Id'], // in addition to set-auth-token
120
+ allowHeaders: ['Content-Type', 'Authorization', 'X-Tenant-Id'],
121
+ },
122
+ }));
123
+ ```
124
+
33
125
  ## Architecture
34
126
 
35
127
  This plugin wraps `@objectstack/hono` to provide a turnkey HTTP server solution for the Runtime. It binds the standard `HttpDispatcher` to a Hono application and starts listening on the configured port.
package/dist/index.d.mts CHANGED
@@ -1,9 +1,60 @@
1
- import { Plugin, PluginContext, IHttpServer, RouteHandler, Middleware } from '@objectstack/core';
1
+ import { IHttpServer, RouteHandler, Middleware, Plugin, PluginContext } from '@objectstack/core';
2
2
  export * from '@objectstack/core';
3
3
  import { RestServerConfig } from '@objectstack/spec/api';
4
4
  import * as hono_types from 'hono/types';
5
5
  import { Hono } from 'hono';
6
6
 
7
+ interface HonoCorsOptions {
8
+ enabled?: boolean;
9
+ origins?: string | string[];
10
+ methods?: string[];
11
+ /**
12
+ * Request headers allowed on preflight (`Access-Control-Allow-Headers`).
13
+ *
14
+ * Defaults to `['Content-Type', 'Authorization', 'X-Requested-With']`,
15
+ * which is sufficient for cookie and bearer-token auth.
16
+ */
17
+ allowHeaders?: string[];
18
+ /**
19
+ * Response headers exposed to JS (`Access-Control-Expose-Headers`).
20
+ *
21
+ * Defaults to `['set-auth-token']` so that better-auth's `bearer()` plugin
22
+ * can hand rotated session tokens to cross-origin clients. User-supplied
23
+ * values are merged with this default — `set-auth-token` is always
24
+ * exposed unless CORS is disabled entirely.
25
+ */
26
+ exposeHeaders?: string[];
27
+ credentials?: boolean;
28
+ maxAge?: number;
29
+ }
30
+ /**
31
+ * Hono Implementation of IHttpServer
32
+ */
33
+ declare class HonoHttpServer implements IHttpServer {
34
+ private port;
35
+ private staticRoot?;
36
+ private app;
37
+ private server;
38
+ private listeningPort;
39
+ constructor(port?: number, staticRoot?: string | undefined);
40
+ private wrap;
41
+ get(path: string, handler: RouteHandler): void;
42
+ post(path: string, handler: RouteHandler): void;
43
+ put(path: string, handler: RouteHandler): void;
44
+ delete(path: string, handler: RouteHandler): void;
45
+ patch(path: string, handler: RouteHandler): void;
46
+ use(pathOrHandler: string | Middleware, handler?: Middleware): void;
47
+ /**
48
+ * Mount a sub-application or router
49
+ */
50
+ mount(path: string, subApp: Hono): void;
51
+ listen(port: number): Promise<void>;
52
+ private tryListen;
53
+ getPort(): number;
54
+ getRawApp(): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
55
+ close(): Promise<void>;
56
+ }
57
+
7
58
  interface StaticMount {
8
59
  root: string;
9
60
  path?: string;
@@ -38,6 +89,13 @@ interface HonoPluginOptions {
38
89
  * @default false
39
90
  */
40
91
  spaFallback?: boolean;
92
+ /**
93
+ * CORS configuration. Set to `false` to disable entirely.
94
+ * Enabled by default with origin '*'.
95
+ * Can also be controlled via environment variables:
96
+ * CORS_ENABLED, CORS_ORIGIN, CORS_CREDENTIALS, CORS_MAX_AGE
97
+ */
98
+ cors?: HonoCorsOptions | false;
41
99
  }
42
100
  /**
43
101
  * Hono Server Plugin
@@ -79,31 +137,64 @@ declare class HonoServerPlugin implements Plugin {
79
137
  }
80
138
 
81
139
  /**
82
- * Hono Implementation of IHttpServer
140
+ * CORS origin pattern matching utilities.
141
+ *
142
+ * Supports the same wildcard syntax as better-auth's `trustedOrigins`:
143
+ * - `*` → matches any origin
144
+ * - `https://*.example.com` → matches any subdomain
145
+ * - `http://localhost:*` → matches any port
146
+ * - Comma-separated list of the above
147
+ *
148
+ * These helpers are shared between the Hono plugin's CORS middleware and
149
+ * consumers that need to apply CORS headers outside the Hono request
150
+ * pipeline (e.g., the Vercel serverless entrypoint's preflight
151
+ * short-circuit in `apps/objectos`). Keeping a single implementation
152
+ * ensures both paths stay consistent — divergence caused bug where
153
+ * wildcard `CORS_ORIGIN` values worked locally but produced browser
154
+ * CORS errors on Vercel.
83
155
  */
84
- declare class HonoHttpServer implements IHttpServer {
85
- private port;
86
- private staticRoot?;
87
- private app;
88
- private server;
89
- private listeningPort;
90
- constructor(port?: number, staticRoot?: string | undefined);
91
- private wrap;
92
- get(path: string, handler: RouteHandler): void;
93
- post(path: string, handler: RouteHandler): void;
94
- put(path: string, handler: RouteHandler): void;
95
- delete(path: string, handler: RouteHandler): void;
96
- patch(path: string, handler: RouteHandler): void;
97
- use(pathOrHandler: string | Middleware, handler?: Middleware): void;
98
- /**
99
- * Mount a sub-application or router
100
- */
101
- mount(path: string, subApp: Hono): void;
102
- listen(port: number): Promise<void>;
103
- private tryListen;
104
- getPort(): number;
105
- getRawApp(): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
106
- close(): Promise<void>;
107
- }
156
+ /**
157
+ * Returns true when the origin points to localhost (any port, http or https).
158
+ *
159
+ * Matches:
160
+ * - `http://localhost`
161
+ * - `http://localhost:3000`
162
+ * - `https://localhost:8443`
163
+ * - `http://127.0.0.1:5173`
164
+ * - `http://[::1]:3000`
165
+ */
166
+ declare function isLocalhostOrigin(origin: string): boolean;
167
+ /**
168
+ * Check if an origin matches a pattern with wildcards.
169
+ *
170
+ * Localhost origins (`http(s)://localhost:<any-port>`, `127.0.0.1`, `[::1]`)
171
+ * are **always allowed** regardless of the pattern — this removes the need to
172
+ * enumerate every development port in `CORS_ORIGIN`.
173
+ *
174
+ * @param origin The origin to check (e.g., `https://app.example.com`)
175
+ * @param pattern The pattern to match against (supports `*` wildcard)
176
+ * @returns true if origin matches the pattern
177
+ */
178
+ declare function matchOriginPattern(origin: string, pattern: string): boolean;
179
+ /**
180
+ * Normalize a single string / comma-separated string / array into a
181
+ * trimmed array of non-empty patterns.
182
+ */
183
+ declare function normalizeOriginPatterns(patterns: string | string[]): string[];
184
+ /**
185
+ * Create a CORS origin matcher function that supports wildcard patterns.
186
+ *
187
+ * The returned function follows Hono's `cors({ origin })` contract:
188
+ * given the request's `Origin` header, it returns the origin to echo
189
+ * back in `Access-Control-Allow-Origin`, or `null` if the origin is not
190
+ * allowed.
191
+ *
192
+ * @param patterns Single pattern, array of patterns, or comma-separated patterns
193
+ */
194
+ declare function createOriginMatcher(patterns: string | string[]): (origin: string) => string | null;
195
+ /**
196
+ * True if any pattern in the given list contains a `*` wildcard.
197
+ */
198
+ declare function hasWildcardPattern(patterns: string | string[]): boolean;
108
199
 
109
- export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount };
200
+ export { type HonoCorsOptions, HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount, createOriginMatcher, hasWildcardPattern, isLocalhostOrigin, matchOriginPattern, normalizeOriginPatterns };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,60 @@
1
- import { Plugin, PluginContext, IHttpServer, RouteHandler, Middleware } from '@objectstack/core';
1
+ import { IHttpServer, RouteHandler, Middleware, Plugin, PluginContext } from '@objectstack/core';
2
2
  export * from '@objectstack/core';
3
3
  import { RestServerConfig } from '@objectstack/spec/api';
4
4
  import * as hono_types from 'hono/types';
5
5
  import { Hono } from 'hono';
6
6
 
7
+ interface HonoCorsOptions {
8
+ enabled?: boolean;
9
+ origins?: string | string[];
10
+ methods?: string[];
11
+ /**
12
+ * Request headers allowed on preflight (`Access-Control-Allow-Headers`).
13
+ *
14
+ * Defaults to `['Content-Type', 'Authorization', 'X-Requested-With']`,
15
+ * which is sufficient for cookie and bearer-token auth.
16
+ */
17
+ allowHeaders?: string[];
18
+ /**
19
+ * Response headers exposed to JS (`Access-Control-Expose-Headers`).
20
+ *
21
+ * Defaults to `['set-auth-token']` so that better-auth's `bearer()` plugin
22
+ * can hand rotated session tokens to cross-origin clients. User-supplied
23
+ * values are merged with this default — `set-auth-token` is always
24
+ * exposed unless CORS is disabled entirely.
25
+ */
26
+ exposeHeaders?: string[];
27
+ credentials?: boolean;
28
+ maxAge?: number;
29
+ }
30
+ /**
31
+ * Hono Implementation of IHttpServer
32
+ */
33
+ declare class HonoHttpServer implements IHttpServer {
34
+ private port;
35
+ private staticRoot?;
36
+ private app;
37
+ private server;
38
+ private listeningPort;
39
+ constructor(port?: number, staticRoot?: string | undefined);
40
+ private wrap;
41
+ get(path: string, handler: RouteHandler): void;
42
+ post(path: string, handler: RouteHandler): void;
43
+ put(path: string, handler: RouteHandler): void;
44
+ delete(path: string, handler: RouteHandler): void;
45
+ patch(path: string, handler: RouteHandler): void;
46
+ use(pathOrHandler: string | Middleware, handler?: Middleware): void;
47
+ /**
48
+ * Mount a sub-application or router
49
+ */
50
+ mount(path: string, subApp: Hono): void;
51
+ listen(port: number): Promise<void>;
52
+ private tryListen;
53
+ getPort(): number;
54
+ getRawApp(): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
55
+ close(): Promise<void>;
56
+ }
57
+
7
58
  interface StaticMount {
8
59
  root: string;
9
60
  path?: string;
@@ -38,6 +89,13 @@ interface HonoPluginOptions {
38
89
  * @default false
39
90
  */
40
91
  spaFallback?: boolean;
92
+ /**
93
+ * CORS configuration. Set to `false` to disable entirely.
94
+ * Enabled by default with origin '*'.
95
+ * Can also be controlled via environment variables:
96
+ * CORS_ENABLED, CORS_ORIGIN, CORS_CREDENTIALS, CORS_MAX_AGE
97
+ */
98
+ cors?: HonoCorsOptions | false;
41
99
  }
42
100
  /**
43
101
  * Hono Server Plugin
@@ -79,31 +137,64 @@ declare class HonoServerPlugin implements Plugin {
79
137
  }
80
138
 
81
139
  /**
82
- * Hono Implementation of IHttpServer
140
+ * CORS origin pattern matching utilities.
141
+ *
142
+ * Supports the same wildcard syntax as better-auth's `trustedOrigins`:
143
+ * - `*` → matches any origin
144
+ * - `https://*.example.com` → matches any subdomain
145
+ * - `http://localhost:*` → matches any port
146
+ * - Comma-separated list of the above
147
+ *
148
+ * These helpers are shared between the Hono plugin's CORS middleware and
149
+ * consumers that need to apply CORS headers outside the Hono request
150
+ * pipeline (e.g., the Vercel serverless entrypoint's preflight
151
+ * short-circuit in `apps/objectos`). Keeping a single implementation
152
+ * ensures both paths stay consistent — divergence caused bug where
153
+ * wildcard `CORS_ORIGIN` values worked locally but produced browser
154
+ * CORS errors on Vercel.
83
155
  */
84
- declare class HonoHttpServer implements IHttpServer {
85
- private port;
86
- private staticRoot?;
87
- private app;
88
- private server;
89
- private listeningPort;
90
- constructor(port?: number, staticRoot?: string | undefined);
91
- private wrap;
92
- get(path: string, handler: RouteHandler): void;
93
- post(path: string, handler: RouteHandler): void;
94
- put(path: string, handler: RouteHandler): void;
95
- delete(path: string, handler: RouteHandler): void;
96
- patch(path: string, handler: RouteHandler): void;
97
- use(pathOrHandler: string | Middleware, handler?: Middleware): void;
98
- /**
99
- * Mount a sub-application or router
100
- */
101
- mount(path: string, subApp: Hono): void;
102
- listen(port: number): Promise<void>;
103
- private tryListen;
104
- getPort(): number;
105
- getRawApp(): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
106
- close(): Promise<void>;
107
- }
156
+ /**
157
+ * Returns true when the origin points to localhost (any port, http or https).
158
+ *
159
+ * Matches:
160
+ * - `http://localhost`
161
+ * - `http://localhost:3000`
162
+ * - `https://localhost:8443`
163
+ * - `http://127.0.0.1:5173`
164
+ * - `http://[::1]:3000`
165
+ */
166
+ declare function isLocalhostOrigin(origin: string): boolean;
167
+ /**
168
+ * Check if an origin matches a pattern with wildcards.
169
+ *
170
+ * Localhost origins (`http(s)://localhost:<any-port>`, `127.0.0.1`, `[::1]`)
171
+ * are **always allowed** regardless of the pattern — this removes the need to
172
+ * enumerate every development port in `CORS_ORIGIN`.
173
+ *
174
+ * @param origin The origin to check (e.g., `https://app.example.com`)
175
+ * @param pattern The pattern to match against (supports `*` wildcard)
176
+ * @returns true if origin matches the pattern
177
+ */
178
+ declare function matchOriginPattern(origin: string, pattern: string): boolean;
179
+ /**
180
+ * Normalize a single string / comma-separated string / array into a
181
+ * trimmed array of non-empty patterns.
182
+ */
183
+ declare function normalizeOriginPatterns(patterns: string | string[]): string[];
184
+ /**
185
+ * Create a CORS origin matcher function that supports wildcard patterns.
186
+ *
187
+ * The returned function follows Hono's `cors({ origin })` contract:
188
+ * given the request's `Origin` header, it returns the origin to echo
189
+ * back in `Access-Control-Allow-Origin`, or `null` if the origin is not
190
+ * allowed.
191
+ *
192
+ * @param patterns Single pattern, array of patterns, or comma-separated patterns
193
+ */
194
+ declare function createOriginMatcher(patterns: string | string[]): (origin: string) => string | null;
195
+ /**
196
+ * True if any pattern in the given list contains a `*` wildcard.
197
+ */
198
+ declare function hasWildcardPattern(patterns: string | string[]): boolean;
108
199
 
109
- export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount };
200
+ export { type HonoCorsOptions, HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount, createOriginMatcher, hasWildcardPattern, isLocalhostOrigin, matchOriginPattern, normalizeOriginPatterns };