@objectstack/plugin-hono-server 4.0.2 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/plugin-hono-server@4.0.2 build /home/runner/work/framework/framework/packages/plugins/plugin-hono-server
2
+ > @objectstack/plugin-hono-server@4.0.4 build /home/runner/work/framework/framework/packages/plugins/plugin-hono-server
3
3
  > tsup --config ../../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.mjs 14.58 KB
14
- ESM dist/index.mjs.map 29.19 KB
15
- ESM ⚡️ Build success in 89ms
16
- CJS dist/index.js 15.52 KB
17
- CJS dist/index.js.map 29.18 KB
18
- CJS ⚡️ Build success in 90ms
13
+ ESM dist/index.mjs 17.42 KB
14
+ ESM dist/index.mjs.map 36.85 KB
15
+ ESM ⚡️ Build success in 102ms
16
+ CJS dist/index.js 18.38 KB
17
+ CJS dist/index.js.map 36.84 KB
18
+ CJS ⚡️ Build success in 104ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 18725ms
21
- DTS dist/index.d.mts 3.29 KB
22
- DTS dist/index.d.ts 3.29 KB
20
+ DTS ⚡️ Build success in 19746ms
21
+ DTS dist/index.d.mts 3.39 KB
22
+ DTS dist/index.d.ts 3.39 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @objectstack/plugin-hono-server
2
2
 
3
+ ## 4.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [326b66b]
8
+ - @objectstack/spec@4.0.4
9
+ - @objectstack/core@4.0.4
10
+
11
+ ## 4.0.3
12
+
13
+ ### Patch Changes
14
+
15
+ - @objectstack/spec@4.0.3
16
+ - @objectstack/core@4.0.3
17
+
3
18
  ## 4.0.2
4
19
 
5
20
  ### Patch Changes
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,75 @@ 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
+
33
103
  ## Architecture
34
104
 
35
105
  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,44 @@
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
+ credentials?: boolean;
12
+ maxAge?: number;
13
+ }
14
+ /**
15
+ * Hono Implementation of IHttpServer
16
+ */
17
+ declare class HonoHttpServer implements IHttpServer {
18
+ private port;
19
+ private staticRoot?;
20
+ private app;
21
+ private server;
22
+ private listeningPort;
23
+ constructor(port?: number, staticRoot?: string | undefined);
24
+ private wrap;
25
+ get(path: string, handler: RouteHandler): void;
26
+ post(path: string, handler: RouteHandler): void;
27
+ put(path: string, handler: RouteHandler): void;
28
+ delete(path: string, handler: RouteHandler): void;
29
+ patch(path: string, handler: RouteHandler): void;
30
+ use(pathOrHandler: string | Middleware, handler?: Middleware): void;
31
+ /**
32
+ * Mount a sub-application or router
33
+ */
34
+ mount(path: string, subApp: Hono): void;
35
+ listen(port: number): Promise<void>;
36
+ private tryListen;
37
+ getPort(): number;
38
+ getRawApp(): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
39
+ close(): Promise<void>;
40
+ }
41
+
7
42
  interface StaticMount {
8
43
  root: string;
9
44
  path?: string;
@@ -38,17 +73,14 @@ interface HonoPluginOptions {
38
73
  * @default false
39
74
  */
40
75
  spaFallback?: boolean;
76
+ /**
77
+ * CORS configuration. Set to `false` to disable entirely.
78
+ * Enabled by default with origin '*'.
79
+ * Can also be controlled via environment variables:
80
+ * CORS_ENABLED, CORS_ORIGIN, CORS_CREDENTIALS, CORS_MAX_AGE
81
+ */
82
+ cors?: HonoCorsOptions | false;
41
83
  }
42
- /**
43
- * Hono Server Plugin
44
- *
45
- * Provides HTTP server capabilities using Hono framework.
46
- * Registers the IHttpServer service so other plugins can register routes.
47
- *
48
- * Route registration is handled by plugins:
49
- * - `@objectstack/rest` → CRUD, metadata, discovery, UI, batch
50
- * - `createDispatcherPlugin()` → auth, graphql, analytics, packages, etc.
51
- */
52
84
  declare class HonoServerPlugin implements Plugin {
53
85
  name: string;
54
86
  type: string;
@@ -78,32 +110,4 @@ declare class HonoServerPlugin implements Plugin {
78
110
  destroy(): Promise<void>;
79
111
  }
80
112
 
81
- /**
82
- * Hono Implementation of IHttpServer
83
- */
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
- }
108
-
109
- export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount };
113
+ export { type HonoCorsOptions, HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,44 @@
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
+ credentials?: boolean;
12
+ maxAge?: number;
13
+ }
14
+ /**
15
+ * Hono Implementation of IHttpServer
16
+ */
17
+ declare class HonoHttpServer implements IHttpServer {
18
+ private port;
19
+ private staticRoot?;
20
+ private app;
21
+ private server;
22
+ private listeningPort;
23
+ constructor(port?: number, staticRoot?: string | undefined);
24
+ private wrap;
25
+ get(path: string, handler: RouteHandler): void;
26
+ post(path: string, handler: RouteHandler): void;
27
+ put(path: string, handler: RouteHandler): void;
28
+ delete(path: string, handler: RouteHandler): void;
29
+ patch(path: string, handler: RouteHandler): void;
30
+ use(pathOrHandler: string | Middleware, handler?: Middleware): void;
31
+ /**
32
+ * Mount a sub-application or router
33
+ */
34
+ mount(path: string, subApp: Hono): void;
35
+ listen(port: number): Promise<void>;
36
+ private tryListen;
37
+ getPort(): number;
38
+ getRawApp(): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
39
+ close(): Promise<void>;
40
+ }
41
+
7
42
  interface StaticMount {
8
43
  root: string;
9
44
  path?: string;
@@ -38,17 +73,14 @@ interface HonoPluginOptions {
38
73
  * @default false
39
74
  */
40
75
  spaFallback?: boolean;
76
+ /**
77
+ * CORS configuration. Set to `false` to disable entirely.
78
+ * Enabled by default with origin '*'.
79
+ * Can also be controlled via environment variables:
80
+ * CORS_ENABLED, CORS_ORIGIN, CORS_CREDENTIALS, CORS_MAX_AGE
81
+ */
82
+ cors?: HonoCorsOptions | false;
41
83
  }
42
- /**
43
- * Hono Server Plugin
44
- *
45
- * Provides HTTP server capabilities using Hono framework.
46
- * Registers the IHttpServer service so other plugins can register routes.
47
- *
48
- * Route registration is handled by plugins:
49
- * - `@objectstack/rest` → CRUD, metadata, discovery, UI, batch
50
- * - `createDispatcherPlugin()` → auth, graphql, analytics, packages, etc.
51
- */
52
84
  declare class HonoServerPlugin implements Plugin {
53
85
  name: string;
54
86
  type: string;
@@ -78,32 +110,4 @@ declare class HonoServerPlugin implements Plugin {
78
110
  destroy(): Promise<void>;
79
111
  }
80
112
 
81
- /**
82
- * Hono Implementation of IHttpServer
83
- */
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
- }
108
-
109
- export { HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount };
113
+ export { type HonoCorsOptions, HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount };
package/dist/index.js CHANGED
@@ -49,8 +49,8 @@ var import_node_server = require("@hono/node-server");
49
49
  var import_serve_static = require("@hono/node-server/serve-static");
50
50
  var HonoHttpServer = class {
51
51
  constructor(port = 3e3, staticRoot) {
52
- this.port = port;
53
- this.staticRoot = staticRoot;
52
+ __publicField(this, "port", port);
53
+ __publicField(this, "staticRoot", staticRoot);
54
54
  __publicField(this, "app");
55
55
  __publicField(this, "server");
56
56
  __publicField(this, "listeningPort");
@@ -229,9 +229,33 @@ var HonoHttpServer = class {
229
229
  };
230
230
 
231
231
  // src/hono-plugin.ts
232
+ var import_cors = require("hono/cors");
232
233
  var import_serve_static2 = require("@hono/node-server/serve-static");
233
234
  var fs = __toESM(require("fs"));
234
235
  var path = __toESM(require("path"));
236
+ function matchOriginPattern(origin, pattern) {
237
+ if (pattern === "*") return true;
238
+ if (pattern === origin) return true;
239
+ const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
240
+ const regex = new RegExp(`^${regexPattern}$`);
241
+ return regex.test(origin);
242
+ }
243
+ function createOriginMatcher(patterns) {
244
+ let patternList;
245
+ if (typeof patterns === "string") {
246
+ patternList = patterns.includes(",") ? patterns.split(",").map((s) => s.trim()).filter(Boolean) : [patterns];
247
+ } else {
248
+ patternList = patterns;
249
+ }
250
+ return (requestOrigin) => {
251
+ for (const pattern of patternList) {
252
+ if (matchOriginPattern(requestOrigin, pattern)) {
253
+ return requestOrigin;
254
+ }
255
+ }
256
+ return null;
257
+ };
258
+ }
235
259
  var HonoServerPlugin = class {
236
260
  constructor(options = {}) {
237
261
  __publicField(this, "name", "com.objectstack.server.hono");
@@ -250,6 +274,46 @@ var HonoServerPlugin = class {
250
274
  ctx.registerService("http.server", this.server);
251
275
  ctx.registerService("http-server", this.server);
252
276
  ctx.logger.debug("HTTP server service registered", { serviceName: "http.server" });
277
+ const corsDisabledByEnv = process.env.CORS_ENABLED === "false";
278
+ if (this.options.cors !== false && !corsDisabledByEnv) {
279
+ const corsOpts = typeof this.options.cors === "object" ? this.options.cors : {};
280
+ const enabled = corsOpts.enabled ?? true;
281
+ if (enabled) {
282
+ let configuredOrigin;
283
+ if (corsOpts.origins) {
284
+ configuredOrigin = corsOpts.origins;
285
+ } else if (process.env.CORS_ORIGIN) {
286
+ const envOrigin = process.env.CORS_ORIGIN.trim();
287
+ configuredOrigin = envOrigin.includes(",") ? envOrigin.split(",").map((s) => s.trim()) : envOrigin;
288
+ } else {
289
+ configuredOrigin = "*";
290
+ }
291
+ const credentials = corsOpts.credentials ?? process.env.CORS_CREDENTIALS !== "false";
292
+ const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);
293
+ let origin;
294
+ const hasWildcard = (patterns) => {
295
+ const list = Array.isArray(patterns) ? patterns : [patterns];
296
+ return list.some((p) => p.includes("*"));
297
+ };
298
+ if (configuredOrigin === "*" && credentials) {
299
+ origin = (requestOrigin) => requestOrigin || "*";
300
+ } else if (hasWildcard(configuredOrigin)) {
301
+ origin = createOriginMatcher(configuredOrigin);
302
+ } else {
303
+ origin = configuredOrigin;
304
+ }
305
+ const rawApp = this.server.getRawApp();
306
+ rawApp.use("*", (0, import_cors.cors)({
307
+ origin,
308
+ allowMethods: corsOpts.methods || ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
309
+ allowHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
310
+ exposeHeaders: [],
311
+ credentials,
312
+ maxAge
313
+ }));
314
+ ctx.logger.debug("CORS middleware enabled", { origin: configuredOrigin, credentials });
315
+ }
316
+ }
253
317
  });
254
318
  /**
255
319
  * Start phase - Configure static files and start listening
@@ -399,30 +463,34 @@ var HonoServerPlugin = class {
399
463
  rawApp.get("/.well-known/objectstack", (c) => c.redirect(`${prefix}/discovery`));
400
464
  rawApp.get(`${prefix}/discovery`, (c) => c.json({ data: discovery }));
401
465
  ctx.logger.info("Registered discovery endpoints", { prefix });
402
- const getBroker = () => ctx.getKernel().broker;
466
+ const getObjectQL = () => ctx.getService("objectql");
403
467
  rawApp.post(`${prefix}/data/:object`, async (c) => {
404
- const broker = getBroker();
405
- if (!broker) return c.json({ error: "Broker not available" }, 500);
468
+ const ql = getObjectQL();
469
+ if (!ql) return c.json({ error: "Data service not available" }, 503);
406
470
  const object = c.req.param("object");
407
471
  const data = await c.req.json().catch(() => ({}));
408
- const result = await broker.call("data.create", { object, data }, {});
409
- return c.json(result);
472
+ const res = await ql.insert(object, data);
473
+ const record = { ...data, ...res };
474
+ return c.json({ object, id: record.id, record });
410
475
  });
411
476
  rawApp.get(`${prefix}/data/:object/:id`, async (c) => {
412
- const broker = getBroker();
413
- if (!broker) return c.json({ error: "Broker not available" }, 500);
477
+ const ql = getObjectQL();
478
+ if (!ql) return c.json({ error: "Data service not available" }, 503);
414
479
  const object = c.req.param("object");
415
480
  const id = c.req.param("id");
416
- const result = await broker.call("data.get", { object, id }, {});
417
- return result ? c.json(result) : c.json({ error: "Not found" }, 404);
481
+ let all = await ql.find(object);
482
+ if (!all) all = [];
483
+ const match = all.find((i) => i.id === id);
484
+ return match ? c.json({ object, id, record: match }) : c.json({ error: "Not found" }, 404);
418
485
  });
419
486
  rawApp.get(`${prefix}/data/:object`, async (c) => {
420
- const broker = getBroker();
421
- if (!broker) return c.json({ error: "Broker not available" }, 500);
487
+ const ql = getObjectQL();
488
+ if (!ql) return c.json({ error: "Data service not available" }, 503);
422
489
  const object = c.req.param("object");
423
- const filters = c.req.query();
424
- const result = await broker.call("data.find", { object, filters }, {});
425
- return c.json(result);
490
+ let all = await ql.find(object);
491
+ if (!Array.isArray(all) && all && all.value) all = all.value;
492
+ if (!all) all = [];
493
+ return c.json({ object, records: all, total: all.length });
426
494
  });
427
495
  ctx.logger.debug("Registered standard CRUD data endpoints", { prefix });
428
496
  }