@kubun/plugin-http 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +6 -2
- package/lib/index.js +154 -1
- package/package.json +21 -20
package/lib/index.d.ts
CHANGED
|
@@ -7,10 +7,12 @@ import { Hono } from 'hono';
|
|
|
7
7
|
export type HTTPPluginOptions = {
|
|
8
8
|
/** Port to listen on. Default: 4321 (falls back to a random available port). */
|
|
9
9
|
port?: number;
|
|
10
|
-
/** Allowed CORS origin(s). Default:
|
|
10
|
+
/** Allowed CORS origin(s). Default: deny cross-origin. Comma-separated strings are split. */
|
|
11
11
|
allowedOrigin?: string | Array<string>;
|
|
12
12
|
/** Namespace prefix for all plugin-managed routes (health, info, protocols). Default: '/'. */
|
|
13
13
|
prefix?: string;
|
|
14
|
+
/** Maximum request body size in bytes, enforced by the transport. Default: 1 MiB. */
|
|
15
|
+
bodyLimit?: number;
|
|
14
16
|
};
|
|
15
17
|
/**
|
|
16
18
|
* API provided by the HTTP infrastructure plugin.
|
|
@@ -30,8 +32,10 @@ export type HTTPPluginAPI = {
|
|
|
30
32
|
export type GraphAppOptions = {
|
|
31
33
|
/** The RPC plugin API (already resolved). */
|
|
32
34
|
rpc: RPCPluginAPI;
|
|
33
|
-
/** Allowed CORS origin(s). Default:
|
|
35
|
+
/** Allowed CORS origin(s). Default: deny cross-origin. */
|
|
34
36
|
allowedOrigin?: string | Array<string>;
|
|
37
|
+
/** Maximum request body size in bytes, enforced by the transport. Default: 1 MiB. */
|
|
38
|
+
bodyLimit?: number;
|
|
35
39
|
};
|
|
36
40
|
/**
|
|
37
41
|
* A mountable Hono sub-app with graph protocol routes.
|
package/lib/index.js
CHANGED
|
@@ -1 +1,154 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import { ServerTransport } from '@enkaku/http-serve';
|
|
2
|
+
import { serve } from '@hono/node-server';
|
|
3
|
+
import { resolveAllowedOrigin } from '@kubun/http-util';
|
|
4
|
+
import { defer } from '@sozai/async';
|
|
5
|
+
import getPort from 'get-port';
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
// ---- Constants ----
|
|
8
|
+
const DEFAULT_PORT = 4321;
|
|
9
|
+
/**
|
|
10
|
+
* Default maximum request body size (1 MiB), matching the Enkaku
|
|
11
|
+
* `ServerTransport` default. The transport is the real backstop — it reads the
|
|
12
|
+
* body with a streaming limit and rejects an oversized request with 413 before
|
|
13
|
+
* buffering it, guarding against memory-exhaustion bodies. Raise via the
|
|
14
|
+
* `bodyLimit` option to admit larger binary-state graph mutations, or lower it
|
|
15
|
+
* to tighten the cap.
|
|
16
|
+
*/ const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
17
|
+
// ---- Helpers ----
|
|
18
|
+
function createProtocolHandler(fetch) {
|
|
19
|
+
return async (c)=>{
|
|
20
|
+
return fetch(c.req.raw);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a standalone Hono app with graph protocol routes.
|
|
25
|
+
*
|
|
26
|
+
* Unlike createHTTPPlugin, this does NOT start an HTTP server or bind a port.
|
|
27
|
+
* The returned app is meant to be mounted on an existing Hono app via `parent.route(prefix, graphApp.app)`.
|
|
28
|
+
*
|
|
29
|
+
* The caller is responsible for:
|
|
30
|
+
* - Creating and awaiting the KubunEngine
|
|
31
|
+
* - Resolving the RPCPluginAPI via engine.getAPI('rpc')
|
|
32
|
+
* - Mounting the returned app and managing the HTTP server lifecycle
|
|
33
|
+
*/ export function createGraphApp(options) {
|
|
34
|
+
const app = new Hono();
|
|
35
|
+
const allowedOrigin = resolveAllowedOrigin(options.allowedOrigin);
|
|
36
|
+
const transport = new ServerTransport({
|
|
37
|
+
allowedOrigin,
|
|
38
|
+
maxRequestBodySize: options.bodyLimit ?? DEFAULT_BODY_LIMIT_BYTES
|
|
39
|
+
});
|
|
40
|
+
const handler = createProtocolHandler(transport.fetch.bind(transport));
|
|
41
|
+
// @ts-expect-error: type instantiation too deep
|
|
42
|
+
const server = options.rpc.serve(transport);
|
|
43
|
+
app.post('/graph', handler);
|
|
44
|
+
app.post('/graph/*', handler);
|
|
45
|
+
app.options('/graph', handler);
|
|
46
|
+
app.options('/graph/*', handler);
|
|
47
|
+
return {
|
|
48
|
+
app,
|
|
49
|
+
async dispose () {
|
|
50
|
+
await server.dispose();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// ---- Plugin factory ----
|
|
55
|
+
/**
|
|
56
|
+
* Creates the HTTP infrastructure plugin.
|
|
57
|
+
*
|
|
58
|
+
* Wraps a Hono app instance, binds it to a port using @hono/node-server,
|
|
59
|
+
* and serves Enkaku protocol handlers over HTTP via ServerTransport.
|
|
60
|
+
* Other plugins can register additional routes via the API.
|
|
61
|
+
*/ export function createHTTPPlugin(options) {
|
|
62
|
+
return (params)=>{
|
|
63
|
+
const app = new Hono();
|
|
64
|
+
const listening = defer();
|
|
65
|
+
const allowedOrigin = resolveAllowedOrigin(options?.allowedOrigin);
|
|
66
|
+
const rawPrefix = options?.prefix ?? '/';
|
|
67
|
+
const normalizedPrefix = rawPrefix === '/' ? '' : rawPrefix.endsWith('/') ? rawPrefix.slice(0, -1) : rawPrefix;
|
|
68
|
+
let resolvedPort;
|
|
69
|
+
let resolvedURL;
|
|
70
|
+
// Protocol registry: name → resolved full path
|
|
71
|
+
const protocols = new Map();
|
|
72
|
+
// Eager setup: built-in routes under the prefix
|
|
73
|
+
app.get(`${normalizedPrefix}/health`, (c)=>c.json({
|
|
74
|
+
status: 'ok'
|
|
75
|
+
}));
|
|
76
|
+
app.get(`${normalizedPrefix}/info`, (c)=>{
|
|
77
|
+
const protocolsObj = {};
|
|
78
|
+
for (const [name, path] of protocols){
|
|
79
|
+
protocolsObj[name] = path;
|
|
80
|
+
}
|
|
81
|
+
return c.json({
|
|
82
|
+
did: params.identity.id,
|
|
83
|
+
protocols: protocolsObj
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
// API object -- getPort/getURL throw before the server is listening
|
|
87
|
+
const api = {
|
|
88
|
+
listening: null,
|
|
89
|
+
registerRoute (method, path, handler) {
|
|
90
|
+
const m = method.toLowerCase();
|
|
91
|
+
// Hono handler type is deeply generic — cast through never to avoid `any`
|
|
92
|
+
app[m](path, handler);
|
|
93
|
+
},
|
|
94
|
+
registerProtocol (name, fetch) {
|
|
95
|
+
const resolvedPath = `${normalizedPrefix}/${name}`;
|
|
96
|
+
protocols.set(name, resolvedPath);
|
|
97
|
+
const handler = createProtocolHandler(fetch);
|
|
98
|
+
app.post(resolvedPath, handler);
|
|
99
|
+
app.post(`${resolvedPath}/*`, handler);
|
|
100
|
+
app.options(resolvedPath, handler);
|
|
101
|
+
app.options(`${resolvedPath}/*`, handler);
|
|
102
|
+
},
|
|
103
|
+
getPort () {
|
|
104
|
+
if (resolvedPort == null) {
|
|
105
|
+
throw new Error('HTTP server is not listening yet');
|
|
106
|
+
}
|
|
107
|
+
return resolvedPort;
|
|
108
|
+
},
|
|
109
|
+
getURL () {
|
|
110
|
+
if (resolvedURL == null) {
|
|
111
|
+
throw new Error('HTTP server is not listening yet');
|
|
112
|
+
}
|
|
113
|
+
return resolvedURL;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
// Start the HTTP server asynchronously
|
|
117
|
+
let httpServer;
|
|
118
|
+
let enkakuServer;
|
|
119
|
+
const listeningPromise = (async ()=>{
|
|
120
|
+
const port = options?.port ?? await getPort({
|
|
121
|
+
port: DEFAULT_PORT
|
|
122
|
+
});
|
|
123
|
+
// Create the ServerTransport and register it as the 'graph' protocol.
|
|
124
|
+
const transport = new ServerTransport({
|
|
125
|
+
allowedOrigin,
|
|
126
|
+
maxRequestBodySize: options?.bodyLimit ?? DEFAULT_BODY_LIMIT_BYTES
|
|
127
|
+
});
|
|
128
|
+
api.registerProtocol('graph', transport.fetch.bind(transport));
|
|
129
|
+
// Get handlers from plugin-rpc's API instead of the engine's merged handler map.
|
|
130
|
+
// This decouples plugin-http from the engine's handler merging mechanism.
|
|
131
|
+
const rpcPlugin = await params.engine.getAPI('rpc');
|
|
132
|
+
// @ts-expect-error: type instantiation too deep
|
|
133
|
+
enkakuServer = rpcPlugin.serve(transport);
|
|
134
|
+
httpServer = serve({
|
|
135
|
+
fetch: app.fetch,
|
|
136
|
+
port
|
|
137
|
+
}, (info)=>{
|
|
138
|
+
resolvedPort = info.port;
|
|
139
|
+
resolvedURL = `http://localhost:${info.port}`;
|
|
140
|
+
listening.resolve();
|
|
141
|
+
});
|
|
142
|
+
await listening.promise;
|
|
143
|
+
})();
|
|
144
|
+
api.listening = listeningPromise;
|
|
145
|
+
return {
|
|
146
|
+
name: 'http',
|
|
147
|
+
api,
|
|
148
|
+
async dispose () {
|
|
149
|
+
await enkakuServer?.dispose();
|
|
150
|
+
httpServer?.close();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
}
|
package/package.json
CHANGED
|
@@ -1,42 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/plugin-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
|
+
"sideEffects": false,
|
|
5
6
|
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"types": "lib/index.d.ts",
|
|
8
7
|
"exports": {
|
|
9
8
|
".": "./lib/index.js"
|
|
10
9
|
},
|
|
10
|
+
"main": "lib/index.js",
|
|
11
|
+
"types": "lib/index.d.ts",
|
|
11
12
|
"files": [
|
|
12
13
|
"lib/*",
|
|
13
14
|
"LICENSE.md"
|
|
14
15
|
],
|
|
15
|
-
"sideEffects": false,
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@enkaku/
|
|
18
|
-
"@enkaku/
|
|
19
|
-
"@enkaku/
|
|
20
|
-
"@
|
|
21
|
-
"@
|
|
17
|
+
"@enkaku/http-serve": "^0.20.0",
|
|
18
|
+
"@enkaku/protocol": "^0.20.0",
|
|
19
|
+
"@enkaku/server": "^0.20.0",
|
|
20
|
+
"@hono/node-server": "^2.1.0",
|
|
21
|
+
"@sozai/async": "^0.2.1",
|
|
22
22
|
"get-port": "^7.2.0",
|
|
23
|
-
"hono": "^4.
|
|
24
|
-
"@kubun/engine": "^0.
|
|
23
|
+
"hono": "^4.13.0",
|
|
24
|
+
"@kubun/engine": "^0.12.0",
|
|
25
|
+
"@kubun/http-util": "^0.12.0"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"@enkaku/http-
|
|
28
|
-
"@kubun/
|
|
29
|
-
"@kubun/
|
|
30
|
-
"@kubun/
|
|
31
|
-
"@kubun/
|
|
28
|
+
"@enkaku/http-fetch": "^0.20.0",
|
|
29
|
+
"@kubun/plugin-rpc": "^0.12.0",
|
|
30
|
+
"@kubun/client": "^0.12.0",
|
|
31
|
+
"@kubun/protocol": "^0.12.0",
|
|
32
|
+
"@kubun/test-utils": "^0.12.0"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
35
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
34
36
|
"build:clean": "del lib",
|
|
35
|
-
"build:js": "swc src -d ./lib --config-file ../../swc.json --strip-leading-paths",
|
|
37
|
+
"build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
36
38
|
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
37
|
-
"
|
|
39
|
+
"test": "pnpm run test:types && pnpm run test:unit",
|
|
38
40
|
"test:types": "tsc --noEmit -p tsconfig.test.json",
|
|
39
|
-
"test:unit": "vitest run"
|
|
40
|
-
"test": "pnpm run test:types && pnpm run test:unit"
|
|
41
|
+
"test:unit": "vitest run"
|
|
41
42
|
}
|
|
42
43
|
}
|