@kubun/plugin-http 0.10.0 → 0.11.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 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: ['*']. Comma-separated strings are split. */
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{defer as t}from"@enkaku/async";import{ServerTransport as e}from"@enkaku/http-server-transport";import{serve as r}from"@hono/node-server";import o from"get-port";import{Hono as n}from"hono";function i(t){return null==t?["*"]:"string"==typeof t?t.split(",").map(t=>t.trim()):t}function s(t){return async e=>t(e.req.raw)}export function createGraphApp(t){let r=new n,o=new e({allowedOrigin:i(t.allowedOrigin)}),p=s(o.fetch.bind(o)),a=t.rpc.serve(o);return r.post("/graph",p),r.post("/graph/*",p),r.options("/graph",p),r.options("/graph/*",p),{app:r,async dispose(){await a.dispose()}}}export function createHTTPPlugin(p){return a=>{let l,g,c,h,u=new n,f=t(),w=i(p?.allowedOrigin),d=p?.prefix??"/",m="/"===d?"":d.endsWith("/")?d.slice(0,-1):d,y=new Map;u.get(`${m}/health`,t=>t.json({status:"ok"})),u.get(`${m}/info`,t=>{let e={};for(let[t,r]of y)e[t]=r;return t.json({did:a.identity.id,protocols:e})});let P={listening:null,registerRoute(t,e,r){u[t.toLowerCase()](e,r)},registerProtocol(t,e){let r=`${m}/${t}`;y.set(t,r);let o=s(e);u.post(r,o),u.post(`${r}/*`,o),u.options(r,o),u.options(`${r}/*`,o)},getPort(){if(null==l)throw Error("HTTP server is not listening yet");return l},getURL(){if(null==g)throw Error("HTTP server is not listening yet");return g}},v=(async()=>{let t=p?.port??await o({port:4321}),n=new e({allowedOrigin:w});P.registerProtocol("graph",n.fetch.bind(n)),h=(await a.engine.getAPI("rpc")).serve(n),c=r({fetch:u.fetch,port:t},t=>{l=t.port,g=`http://localhost:${t.port}`,f.resolve()}),await f.promise})();return P.listening=v,{name:"http",api:P,async dispose(){await h?.dispose(),c?.close()}}}}
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/plugin-http",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "license": "see LICENSE.md",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -14,25 +14,26 @@
14
14
  ],
15
15
  "sideEffects": false,
16
16
  "dependencies": {
17
- "@enkaku/async": "^0.16.0",
18
- "@enkaku/http-server-transport": "^0.16.0",
19
- "@enkaku/protocol": "^0.16.0",
20
- "@enkaku/server": "^0.16.0",
21
- "@hono/node-server": "^2.0.4",
17
+ "@sozai/async": "^0.1.0",
18
+ "@enkaku/http-serve": "^0.18.1",
19
+ "@enkaku/protocol": "^0.18.1",
20
+ "@enkaku/server": "^0.18.1",
21
+ "@hono/node-server": "^2.0.8",
22
22
  "get-port": "^7.2.0",
23
- "hono": "^4.12.23",
24
- "@kubun/engine": "^0.10.0"
23
+ "hono": "^4.12.27",
24
+ "@kubun/http-util": "^0.11.0",
25
+ "@kubun/engine": "^0.11.0"
25
26
  },
26
27
  "devDependencies": {
27
- "@enkaku/http-client-transport": "^0.16.0",
28
- "@kubun/client": "^0.10.0",
29
- "@kubun/plugin-rpc": "^0.10.0",
30
- "@kubun/test-utils": "^0.10.0",
31
- "@kubun/protocol": "^0.10.0"
28
+ "@enkaku/http-fetch": "^0.18.1",
29
+ "@kubun/plugin-rpc": "^0.11.0",
30
+ "@kubun/client": "^0.11.0",
31
+ "@kubun/test-utils": "^0.11.0",
32
+ "@kubun/protocol": "^0.11.0"
32
33
  },
33
34
  "scripts": {
34
35
  "build:clean": "del lib",
35
- "build:js": "swc src -d ./lib --config-file ../../swc.json --strip-leading-paths",
36
+ "build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
36
37
  "build:types": "tsc --emitDeclarationOnly --skipLibCheck",
37
38
  "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
38
39
  "test:types": "tsc --noEmit -p tsconfig.test.json",