@objectstack/plugin-hono-server 4.0.4 → 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 +22 -0
- package/dist/index.d.mts +88 -1
- package/dist/index.d.ts +88 -1
- package/dist/index.js +190 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +190 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +33 -8
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -688
- package/objectstack.config.ts +0 -240
- package/src/adapter.ts +0 -228
- package/src/hono-plugin.test.ts +0 -236
- package/src/hono-plugin.ts +0 -456
- package/src/index.ts +0 -5
- package/src/pattern-matcher.test.ts +0 -180
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -22
package/README.md
CHANGED
|
@@ -100,6 +100,28 @@ kernel.use(new HonoServerPlugin({
|
|
|
100
100
|
}));
|
|
101
101
|
```
|
|
102
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
|
+
|
|
103
125
|
## Architecture
|
|
104
126
|
|
|
105
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
|
@@ -8,6 +8,22 @@ interface HonoCorsOptions {
|
|
|
8
8
|
enabled?: boolean;
|
|
9
9
|
origins?: string | string[];
|
|
10
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[];
|
|
11
27
|
credentials?: boolean;
|
|
12
28
|
maxAge?: number;
|
|
13
29
|
}
|
|
@@ -81,6 +97,16 @@ interface HonoPluginOptions {
|
|
|
81
97
|
*/
|
|
82
98
|
cors?: HonoCorsOptions | false;
|
|
83
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Hono Server Plugin
|
|
102
|
+
*
|
|
103
|
+
* Provides HTTP server capabilities using Hono framework.
|
|
104
|
+
* Registers the IHttpServer service so other plugins can register routes.
|
|
105
|
+
*
|
|
106
|
+
* Route registration is handled by plugins:
|
|
107
|
+
* - `@objectstack/rest` → CRUD, metadata, discovery, UI, batch
|
|
108
|
+
* - `createDispatcherPlugin()` → auth, graphql, analytics, packages, etc.
|
|
109
|
+
*/
|
|
84
110
|
declare class HonoServerPlugin implements Plugin {
|
|
85
111
|
name: string;
|
|
86
112
|
type: string;
|
|
@@ -110,4 +136,65 @@ declare class HonoServerPlugin implements Plugin {
|
|
|
110
136
|
destroy(): Promise<void>;
|
|
111
137
|
}
|
|
112
138
|
|
|
113
|
-
|
|
139
|
+
/**
|
|
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.
|
|
155
|
+
*/
|
|
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;
|
|
199
|
+
|
|
200
|
+
export { type HonoCorsOptions, HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount, createOriginMatcher, hasWildcardPattern, isLocalhostOrigin, matchOriginPattern, normalizeOriginPatterns };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,22 @@ interface HonoCorsOptions {
|
|
|
8
8
|
enabled?: boolean;
|
|
9
9
|
origins?: string | string[];
|
|
10
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[];
|
|
11
27
|
credentials?: boolean;
|
|
12
28
|
maxAge?: number;
|
|
13
29
|
}
|
|
@@ -81,6 +97,16 @@ interface HonoPluginOptions {
|
|
|
81
97
|
*/
|
|
82
98
|
cors?: HonoCorsOptions | false;
|
|
83
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Hono Server Plugin
|
|
102
|
+
*
|
|
103
|
+
* Provides HTTP server capabilities using Hono framework.
|
|
104
|
+
* Registers the IHttpServer service so other plugins can register routes.
|
|
105
|
+
*
|
|
106
|
+
* Route registration is handled by plugins:
|
|
107
|
+
* - `@objectstack/rest` → CRUD, metadata, discovery, UI, batch
|
|
108
|
+
* - `createDispatcherPlugin()` → auth, graphql, analytics, packages, etc.
|
|
109
|
+
*/
|
|
84
110
|
declare class HonoServerPlugin implements Plugin {
|
|
85
111
|
name: string;
|
|
86
112
|
type: string;
|
|
@@ -110,4 +136,65 @@ declare class HonoServerPlugin implements Plugin {
|
|
|
110
136
|
destroy(): Promise<void>;
|
|
111
137
|
}
|
|
112
138
|
|
|
113
|
-
|
|
139
|
+
/**
|
|
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.
|
|
155
|
+
*/
|
|
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;
|
|
199
|
+
|
|
200
|
+
export { type HonoCorsOptions, HonoHttpServer, type HonoPluginOptions, HonoServerPlugin, type StaticMount, createOriginMatcher, hasWildcardPattern, isLocalhostOrigin, matchOriginPattern, normalizeOriginPatterns };
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
34
34
|
var index_exports = {};
|
|
35
35
|
__export(index_exports, {
|
|
36
36
|
HonoHttpServer: () => HonoHttpServer,
|
|
37
|
-
HonoServerPlugin: () => HonoServerPlugin
|
|
37
|
+
HonoServerPlugin: () => HonoServerPlugin,
|
|
38
|
+
createOriginMatcher: () => createOriginMatcher,
|
|
39
|
+
hasWildcardPattern: () => hasWildcardPattern,
|
|
40
|
+
isLocalhostOrigin: () => isLocalhostOrigin,
|
|
41
|
+
matchOriginPattern: () => matchOriginPattern,
|
|
42
|
+
normalizeOriginPatterns: () => normalizeOriginPatterns
|
|
38
43
|
});
|
|
39
44
|
module.exports = __toCommonJS(index_exports);
|
|
40
45
|
|
|
@@ -60,7 +65,9 @@ var HonoHttpServer = class {
|
|
|
60
65
|
wrap(handler) {
|
|
61
66
|
return async (c) => {
|
|
62
67
|
let body = {};
|
|
63
|
-
|
|
68
|
+
const contentType = c.req.header("content-type") ?? "";
|
|
69
|
+
const isOctetStream = contentType.includes("application/octet-stream");
|
|
70
|
+
if (contentType.includes("application/json")) {
|
|
64
71
|
try {
|
|
65
72
|
body = await c.req.json();
|
|
66
73
|
} catch (e) {
|
|
@@ -69,19 +76,31 @@ var HonoHttpServer = class {
|
|
|
69
76
|
} catch (e2) {
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
|
-
} else {
|
|
79
|
+
} else if (!isOctetStream) {
|
|
73
80
|
try {
|
|
74
81
|
body = await c.req.parseBody();
|
|
75
82
|
} catch (e) {
|
|
76
83
|
}
|
|
77
84
|
}
|
|
85
|
+
const rawHeaders = c.req.header();
|
|
86
|
+
if (!rawHeaders.host) {
|
|
87
|
+
try {
|
|
88
|
+
const u = new URL(c.req.url);
|
|
89
|
+
if (u.host) rawHeaders.host = u.host;
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
78
93
|
const req = {
|
|
79
94
|
params: c.req.param(),
|
|
80
95
|
query: c.req.query(),
|
|
81
96
|
body,
|
|
82
|
-
headers:
|
|
97
|
+
headers: rawHeaders,
|
|
83
98
|
method: c.req.method,
|
|
84
|
-
path: c.req.path
|
|
99
|
+
path: c.req.path,
|
|
100
|
+
rawBody: async () => {
|
|
101
|
+
const ab = await c.req.arrayBuffer();
|
|
102
|
+
return Buffer.from(ab);
|
|
103
|
+
}
|
|
85
104
|
};
|
|
86
105
|
let capturedResponse;
|
|
87
106
|
let streamController = null;
|
|
@@ -136,13 +155,13 @@ var HonoHttpServer = class {
|
|
|
136
155
|
streamController?.close();
|
|
137
156
|
resolve2(null);
|
|
138
157
|
}
|
|
139
|
-
}).catch(() => {
|
|
158
|
+
}).catch((err) => {
|
|
140
159
|
streamController?.close();
|
|
141
160
|
resolve2(null);
|
|
142
161
|
});
|
|
143
162
|
});
|
|
144
163
|
const streamResponse = await streamPromise;
|
|
145
|
-
return streamResponse ?? capturedResponse;
|
|
164
|
+
return streamResponse ?? capturedResponse ?? c.json({ error: "No response from handler" }, 500);
|
|
146
165
|
};
|
|
147
166
|
}
|
|
148
167
|
get(path2, handler) {
|
|
@@ -163,11 +182,23 @@ var HonoHttpServer = class {
|
|
|
163
182
|
use(pathOrHandler, handler) {
|
|
164
183
|
if (typeof pathOrHandler === "string" && handler) {
|
|
165
184
|
this.app.use(pathOrHandler, async (c, next) => {
|
|
166
|
-
|
|
185
|
+
let nextCalled = false;
|
|
186
|
+
const wrappedNext = () => {
|
|
187
|
+
nextCalled = true;
|
|
188
|
+
return next();
|
|
189
|
+
};
|
|
190
|
+
await handler({}, {}, wrappedNext);
|
|
191
|
+
if (!nextCalled) await next();
|
|
167
192
|
});
|
|
168
193
|
} else if (typeof pathOrHandler === "function") {
|
|
169
194
|
this.app.use("*", async (c, next) => {
|
|
170
|
-
|
|
195
|
+
let nextCalled = false;
|
|
196
|
+
const wrappedNext = () => {
|
|
197
|
+
nextCalled = true;
|
|
198
|
+
return next();
|
|
199
|
+
};
|
|
200
|
+
await pathOrHandler({}, {}, wrappedNext);
|
|
201
|
+
if (!nextCalled) await next();
|
|
171
202
|
});
|
|
172
203
|
}
|
|
173
204
|
}
|
|
@@ -222,9 +253,13 @@ var HonoHttpServer = class {
|
|
|
222
253
|
return this.app;
|
|
223
254
|
}
|
|
224
255
|
async close() {
|
|
225
|
-
if (this.server
|
|
226
|
-
|
|
256
|
+
if (!this.server) return;
|
|
257
|
+
if (typeof this.server.closeAllConnections === "function") {
|
|
258
|
+
this.server.closeAllConnections();
|
|
227
259
|
}
|
|
260
|
+
await new Promise((resolve2, reject) => {
|
|
261
|
+
this.server.close((err) => err ? reject(err) : resolve2());
|
|
262
|
+
});
|
|
228
263
|
}
|
|
229
264
|
};
|
|
230
265
|
|
|
@@ -233,21 +268,29 @@ var import_cors = require("hono/cors");
|
|
|
233
268
|
var import_serve_static2 = require("@hono/node-server/serve-static");
|
|
234
269
|
var fs = __toESM(require("fs"));
|
|
235
270
|
var path = __toESM(require("path"));
|
|
271
|
+
|
|
272
|
+
// src/pattern-matcher.ts
|
|
273
|
+
function isLocalhostOrigin(origin) {
|
|
274
|
+
return /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(origin);
|
|
275
|
+
}
|
|
236
276
|
function matchOriginPattern(origin, pattern) {
|
|
277
|
+
if (isLocalhostOrigin(origin)) return true;
|
|
237
278
|
if (pattern === "*") return true;
|
|
238
279
|
if (pattern === origin) return true;
|
|
239
280
|
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
240
281
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
241
282
|
return regex.test(origin);
|
|
242
283
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
patternList = patterns.includes(",") ? patterns.split(",").map((s) => s.trim()).filter(Boolean) : [patterns];
|
|
247
|
-
} else {
|
|
248
|
-
patternList = patterns;
|
|
284
|
+
function normalizeOriginPatterns(patterns) {
|
|
285
|
+
if (Array.isArray(patterns)) {
|
|
286
|
+
return patterns.map((p) => p.trim()).filter(Boolean);
|
|
249
287
|
}
|
|
288
|
+
return patterns.includes(",") ? patterns.split(",").map((s) => s.trim()).filter(Boolean) : [patterns.trim()].filter(Boolean);
|
|
289
|
+
}
|
|
290
|
+
function createOriginMatcher(patterns) {
|
|
291
|
+
const patternList = normalizeOriginPatterns(patterns);
|
|
250
292
|
return (requestOrigin) => {
|
|
293
|
+
if (!requestOrigin) return null;
|
|
251
294
|
for (const pattern of patternList) {
|
|
252
295
|
if (matchOriginPattern(requestOrigin, pattern)) {
|
|
253
296
|
return requestOrigin;
|
|
@@ -256,6 +299,12 @@ function createOriginMatcher(patterns) {
|
|
|
256
299
|
return null;
|
|
257
300
|
};
|
|
258
301
|
}
|
|
302
|
+
function hasWildcardPattern(patterns) {
|
|
303
|
+
const list = Array.isArray(patterns) ? patterns : [patterns];
|
|
304
|
+
return list.some((p) => p.includes("*"));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/hono-plugin.ts
|
|
259
308
|
var HonoServerPlugin = class {
|
|
260
309
|
constructor(options = {}) {
|
|
261
310
|
__publicField(this, "name", "com.objectstack.server.hono");
|
|
@@ -291,23 +340,27 @@ var HonoServerPlugin = class {
|
|
|
291
340
|
const credentials = corsOpts.credentials ?? process.env.CORS_CREDENTIALS !== "false";
|
|
292
341
|
const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);
|
|
293
342
|
let origin;
|
|
294
|
-
const hasWildcard = (patterns) => {
|
|
295
|
-
const list = Array.isArray(patterns) ? patterns : [patterns];
|
|
296
|
-
return list.some((p) => p.includes("*"));
|
|
297
|
-
};
|
|
298
343
|
if (configuredOrigin === "*" && credentials) {
|
|
299
344
|
origin = (requestOrigin) => requestOrigin || "*";
|
|
300
|
-
} else if (
|
|
345
|
+
} else if (hasWildcardPattern(configuredOrigin)) {
|
|
301
346
|
origin = createOriginMatcher(configuredOrigin);
|
|
302
347
|
} else {
|
|
303
|
-
|
|
348
|
+
const matcher = createOriginMatcher(configuredOrigin);
|
|
349
|
+
origin = (requestOrigin) => matcher(requestOrigin);
|
|
304
350
|
}
|
|
305
351
|
const rawApp = this.server.getRawApp();
|
|
352
|
+
const defaultAllowHeaders = ["Content-Type", "Authorization", "X-Requested-With", "X-Tenant-ID", "X-Project-Id"];
|
|
353
|
+
const defaultExposeHeaders = ["set-auth-token"];
|
|
354
|
+
const allowHeaders = corsOpts.allowHeaders ?? defaultAllowHeaders;
|
|
355
|
+
const exposeHeaders = Array.from(/* @__PURE__ */ new Set([
|
|
356
|
+
...defaultExposeHeaders,
|
|
357
|
+
...corsOpts.exposeHeaders ?? []
|
|
358
|
+
]));
|
|
306
359
|
rawApp.use("*", (0, import_cors.cors)({
|
|
307
360
|
origin,
|
|
308
361
|
allowMethods: corsOpts.methods || ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
|
|
309
|
-
allowHeaders
|
|
310
|
-
exposeHeaders
|
|
362
|
+
allowHeaders,
|
|
363
|
+
exposeHeaders,
|
|
311
364
|
credentials,
|
|
312
365
|
maxAge
|
|
313
366
|
}));
|
|
@@ -408,6 +461,10 @@ var HonoServerPlugin = class {
|
|
|
408
461
|
});
|
|
409
462
|
}
|
|
410
463
|
}
|
|
464
|
+
const rawAppForNotFound = this.server.getRawApp();
|
|
465
|
+
if (typeof rawAppForNotFound.notFound === "function") {
|
|
466
|
+
rawAppForNotFound.notFound((c) => c.json({ error: "Not found" }, 404));
|
|
467
|
+
}
|
|
411
468
|
ctx.hook("kernel:ready", async () => {
|
|
412
469
|
if (this.options.registerStandardEndpoints) {
|
|
413
470
|
this.registerDiscoveryAndCrudEndpoints(ctx);
|
|
@@ -464,33 +521,124 @@ var HonoServerPlugin = class {
|
|
|
464
521
|
rawApp.get(`${prefix}/discovery`, (c) => c.json({ data: discovery }));
|
|
465
522
|
ctx.logger.info("Registered discovery endpoints", { prefix });
|
|
466
523
|
const getObjectQL = () => ctx.getService("objectql");
|
|
524
|
+
const resolveCtx = async (c) => {
|
|
525
|
+
try {
|
|
526
|
+
const authService = ctx.getService("auth");
|
|
527
|
+
if (!authService) return void 0;
|
|
528
|
+
let api = authService.api;
|
|
529
|
+
if (!api && typeof authService.getApi === "function") {
|
|
530
|
+
api = await authService.getApi();
|
|
531
|
+
}
|
|
532
|
+
if (!api?.getSession) return void 0;
|
|
533
|
+
const session = await api.getSession({ headers: c.req.raw.headers });
|
|
534
|
+
if (!session?.user?.id) return void 0;
|
|
535
|
+
const userId = session.user.id;
|
|
536
|
+
const tenantId = session.session?.activeOrganizationId ?? void 0;
|
|
537
|
+
const permissions = [];
|
|
538
|
+
const roles = [];
|
|
539
|
+
try {
|
|
540
|
+
const ql = getObjectQL();
|
|
541
|
+
const sysCtx = { context: { isSystem: true } };
|
|
542
|
+
const memberRows = await ql?.find?.(
|
|
543
|
+
"sys_member",
|
|
544
|
+
{
|
|
545
|
+
where: tenantId ? { user_id: userId, organization_id: tenantId } : { user_id: userId },
|
|
546
|
+
limit: 50,
|
|
547
|
+
...sysCtx
|
|
548
|
+
}
|
|
549
|
+
).catch(() => []);
|
|
550
|
+
for (const m of memberRows ?? []) {
|
|
551
|
+
if (typeof m.role === "string") {
|
|
552
|
+
for (const r of m.role.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
553
|
+
if (!roles.includes(r)) roles.push(r);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const upsRows = await ql?.find?.(
|
|
558
|
+
"sys_user_permission_set",
|
|
559
|
+
{ where: { user_id: userId }, limit: 100, ...sysCtx }
|
|
560
|
+
).catch(() => []);
|
|
561
|
+
const psIds = /* @__PURE__ */ new Set();
|
|
562
|
+
for (const r of upsRows ?? []) {
|
|
563
|
+
const orgScope = r.organization_id ?? null;
|
|
564
|
+
if (!orgScope || tenantId && orgScope === tenantId) {
|
|
565
|
+
const pid = r.permission_set_id ?? r.permissionSetId;
|
|
566
|
+
if (pid) psIds.add(pid);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (psIds.size > 0) {
|
|
570
|
+
const psRows = await ql?.find?.(
|
|
571
|
+
"sys_permission_set",
|
|
572
|
+
{ where: { id: { $in: Array.from(psIds) } }, limit: 500, ...sysCtx }
|
|
573
|
+
).catch(() => []);
|
|
574
|
+
for (const ps of psRows ?? []) {
|
|
575
|
+
if (ps.name && !permissions.includes(ps.name)) permissions.push(ps.name);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} catch {
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
userId,
|
|
582
|
+
tenantId,
|
|
583
|
+
roles,
|
|
584
|
+
permissions,
|
|
585
|
+
isSystem: false
|
|
586
|
+
};
|
|
587
|
+
} catch {
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
467
591
|
rawApp.post(`${prefix}/data/:object`, async (c) => {
|
|
468
592
|
const ql = getObjectQL();
|
|
469
593
|
if (!ql) return c.json({ error: "Data service not available" }, 503);
|
|
470
594
|
const object = c.req.param("object");
|
|
471
595
|
const data = await c.req.json().catch(() => ({}));
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
596
|
+
const execCtx = await resolveCtx(c);
|
|
597
|
+
try {
|
|
598
|
+
const res = await ql.insert(object, data, { context: execCtx });
|
|
599
|
+
const record = { ...data, ...res };
|
|
600
|
+
return c.json({ object, id: record.id, record });
|
|
601
|
+
} catch (err) {
|
|
602
|
+
if (err?.code === "PERMISSION_DENIED" || err?.name === "PermissionDeniedError") {
|
|
603
|
+
return c.json({ error: err.message ?? "Forbidden" }, 403);
|
|
604
|
+
}
|
|
605
|
+
throw err;
|
|
606
|
+
}
|
|
475
607
|
});
|
|
476
608
|
rawApp.get(`${prefix}/data/:object/:id`, async (c) => {
|
|
477
609
|
const ql = getObjectQL();
|
|
478
610
|
if (!ql) return c.json({ error: "Data service not available" }, 503);
|
|
479
611
|
const object = c.req.param("object");
|
|
480
612
|
const id = c.req.param("id");
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
613
|
+
const execCtx = await resolveCtx(c);
|
|
614
|
+
try {
|
|
615
|
+
let all = await ql.find(object, { context: execCtx });
|
|
616
|
+
if (!all) all = [];
|
|
617
|
+
const match = all.find((i) => i.id === id);
|
|
618
|
+
return match ? c.json({ object, id, record: match }) : c.json({ error: "Not found" }, 404);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
if (err?.code === "PERMISSION_DENIED" || err?.name === "PermissionDeniedError") {
|
|
621
|
+
return c.json({ error: err.message ?? "Forbidden" }, 403);
|
|
622
|
+
}
|
|
623
|
+
throw err;
|
|
624
|
+
}
|
|
485
625
|
});
|
|
486
626
|
rawApp.get(`${prefix}/data/:object`, async (c) => {
|
|
487
627
|
const ql = getObjectQL();
|
|
488
628
|
if (!ql) return c.json({ error: "Data service not available" }, 503);
|
|
489
629
|
const object = c.req.param("object");
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
630
|
+
const execCtx = await resolveCtx(c);
|
|
631
|
+
try {
|
|
632
|
+
let all = await ql.find(object, { context: execCtx });
|
|
633
|
+
if (!Array.isArray(all) && all && all.value) all = all.value;
|
|
634
|
+
if (!all) all = [];
|
|
635
|
+
return c.json({ object, records: all, total: all.length });
|
|
636
|
+
} catch (err) {
|
|
637
|
+
if (err?.code === "PERMISSION_DENIED" || err?.name === "PermissionDeniedError") {
|
|
638
|
+
return c.json({ error: err.message ?? "Forbidden" }, 403);
|
|
639
|
+
}
|
|
640
|
+
throw err;
|
|
641
|
+
}
|
|
494
642
|
});
|
|
495
643
|
ctx.logger.debug("Registered standard CRUD data endpoints", { prefix });
|
|
496
644
|
}
|
|
@@ -512,6 +660,11 @@ __reExport(index_exports, adapter_exports, module.exports);
|
|
|
512
660
|
// Annotate the CommonJS export names for ESM import in node:
|
|
513
661
|
0 && (module.exports = {
|
|
514
662
|
HonoHttpServer,
|
|
515
|
-
HonoServerPlugin
|
|
663
|
+
HonoServerPlugin,
|
|
664
|
+
createOriginMatcher,
|
|
665
|
+
hasWildcardPattern,
|
|
666
|
+
isLocalhostOrigin,
|
|
667
|
+
matchOriginPattern,
|
|
668
|
+
normalizeOriginPatterns
|
|
516
669
|
});
|
|
517
670
|
//# sourceMappingURL=index.js.map
|