@kb-labs/adapters-service-transport-http 2.96.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/README.md +13 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @kb-labs/adapters-service-transport-http
|
|
2
|
+
|
|
3
|
+
> Part of [KB Labs](https://github.com/KirillBaranov/kb-labs) ecosystem.
|
|
4
|
+
|
|
5
|
+
HTTP/unix-socket implementation of `IServiceTransport` using undici connection pools. Platform-only adapter — never exposed to plugin context.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
| Property | Value |
|
|
10
|
+
|----------|-------|
|
|
11
|
+
| Interface | `IServiceTransport` |
|
|
12
|
+
| Backend | undici (HTTP + unix domain sockets) |
|
|
13
|
+
| Layer | Adapter (Layer 2) |
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { IServiceTransport, ServiceConnectionInfo, ServiceListenAddress, ServiceTransportRequest, ServiceTransportResponse, ServiceTransportStream, ServiceTransportHealth } from '@kb-labs/sdk';
|
|
2
|
+
export { IServiceTransport, ServiceConnectionInfo, ServiceTransportHealth, ServiceTransportRequest, ServiceTransportResponse, ServiceTransportStream } from '@kb-labs/sdk';
|
|
3
|
+
import { AdapterManifest } from '@kb-labs/sdk/adapters';
|
|
4
|
+
|
|
5
|
+
interface HttpServiceTransportConfig {
|
|
6
|
+
services: Record<string, {
|
|
7
|
+
url: string;
|
|
8
|
+
socketPath?: string;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Per-environment TCP port shift, added to every service url's port. Sockets
|
|
13
|
+
* are unaffected (they isolate via KB_SOCKET_HASH path, not port). This is a
|
|
14
|
+
* LOCAL mechanism for running multiple environments on one host; in
|
|
15
|
+
* cloud/k8s the offset is 0 and isolation comes from namespace/DNS. Falls
|
|
16
|
+
* back to the KB_NET_OFFSET env var when not set in config.
|
|
17
|
+
*/
|
|
18
|
+
offset?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Shift the port of a TCP url by offset, leaving scheme/host/path intact.
|
|
22
|
+
* No-op for offset 0, urls without an explicit port (e.g. socket placeholders),
|
|
23
|
+
* or unparseable urls. The single helper used for both route and bind so the
|
|
24
|
+
* two never drift.
|
|
25
|
+
*/
|
|
26
|
+
declare function applyPortOffset(url: string, offset: number): string;
|
|
27
|
+
declare class HttpServiceTransport implements IServiceTransport {
|
|
28
|
+
private readonly config;
|
|
29
|
+
private readonly pools;
|
|
30
|
+
private readonly offset;
|
|
31
|
+
constructor(config: HttpServiceTransportConfig);
|
|
32
|
+
connectionInfo(serviceId: string): ServiceConnectionInfo | undefined;
|
|
33
|
+
listenAddress(serviceId: string): ServiceListenAddress | undefined;
|
|
34
|
+
call(serviceId: string, req: ServiceTransportRequest): Promise<ServiceTransportResponse>;
|
|
35
|
+
stream(serviceId: string, req: ServiceTransportRequest): Promise<ServiceTransportStream>;
|
|
36
|
+
private _request;
|
|
37
|
+
health(): Promise<ServiceTransportHealth>;
|
|
38
|
+
/** Drain and close all connection pools. */
|
|
39
|
+
destroy(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare const manifest: AdapterManifest;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @module @kb-labs/adapters-service-transport-http
|
|
46
|
+
* HTTP/unix-socket implementation of IServiceTransport.
|
|
47
|
+
*
|
|
48
|
+
* Uses undici connection pools — supports both TCP and unix domain sockets.
|
|
49
|
+
* Platform-only: never exposed to plugin context.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
declare function createAdapter(config: HttpServiceTransportConfig): HttpServiceTransport;
|
|
53
|
+
|
|
54
|
+
export { HttpServiceTransport, type HttpServiceTransportConfig, applyPortOffset, createAdapter, manifest };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Pool } from 'undici';
|
|
2
|
+
|
|
3
|
+
// src/transport.ts
|
|
4
|
+
function applyPortOffset(url, offset) {
|
|
5
|
+
if (!offset) return url;
|
|
6
|
+
try {
|
|
7
|
+
const u = new URL(url);
|
|
8
|
+
if (!u.port) return url;
|
|
9
|
+
u.port = String(Number(u.port) + offset);
|
|
10
|
+
return u.toString().replace(/\/$/, "");
|
|
11
|
+
} catch {
|
|
12
|
+
return url;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function assertResolvedSocketPath(socketPath, serviceId) {
|
|
16
|
+
if (/\$\{[^}]+\}/.test(socketPath)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Service "${serviceId}" has an unresolved placeholder in socketPath: "${socketPath}". Check that the required environment variable is set (e.g. KB_SOCKET_HASH).`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
var HttpServiceTransport = class {
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
this.offset = config.offset ?? (Number(process.env.KB_NET_OFFSET) || 0);
|
|
26
|
+
for (const [id, svc] of Object.entries(config.services)) {
|
|
27
|
+
const base = svc.socketPath ? svc.url : applyPortOffset(svc.url, this.offset);
|
|
28
|
+
this.pools.set(id, new Pool(base, {
|
|
29
|
+
...svc.socketPath ? { socketPath: svc.socketPath } : {},
|
|
30
|
+
connections: 10,
|
|
31
|
+
pipelining: 0
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
config;
|
|
36
|
+
pools = /* @__PURE__ */ new Map();
|
|
37
|
+
offset;
|
|
38
|
+
connectionInfo(serviceId) {
|
|
39
|
+
const svc = this.config.services[serviceId];
|
|
40
|
+
if (!svc) return void 0;
|
|
41
|
+
if (svc.socketPath) assertResolvedSocketPath(svc.socketPath, serviceId);
|
|
42
|
+
const baseUrl = svc.socketPath ? svc.url : applyPortOffset(svc.url, this.offset);
|
|
43
|
+
return { baseUrl, socketPath: svc.socketPath };
|
|
44
|
+
}
|
|
45
|
+
listenAddress(serviceId) {
|
|
46
|
+
const svc = this.config.services[serviceId];
|
|
47
|
+
if (!svc) return void 0;
|
|
48
|
+
if (svc.socketPath) {
|
|
49
|
+
assertResolvedSocketPath(svc.socketPath, serviceId);
|
|
50
|
+
return { socketPath: svc.socketPath };
|
|
51
|
+
}
|
|
52
|
+
let port;
|
|
53
|
+
try {
|
|
54
|
+
const u = new URL(applyPortOffset(svc.url, this.offset));
|
|
55
|
+
if (!u.port) return void 0;
|
|
56
|
+
port = Number(u.port);
|
|
57
|
+
} catch {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
return { port };
|
|
61
|
+
}
|
|
62
|
+
async call(serviceId, req) {
|
|
63
|
+
const { statusCode, headers, body } = await this._request(serviceId, req);
|
|
64
|
+
const payload = await body.json().catch(async () => {
|
|
65
|
+
const text = await body.text().catch(() => void 0);
|
|
66
|
+
return text ?? void 0;
|
|
67
|
+
});
|
|
68
|
+
return { ok: statusCode < 400, statusCode, payload, metadata: headers };
|
|
69
|
+
}
|
|
70
|
+
async stream(serviceId, req) {
|
|
71
|
+
const { statusCode, headers, body } = await this._request(serviceId, req);
|
|
72
|
+
return {
|
|
73
|
+
ok: statusCode < 400,
|
|
74
|
+
statusCode,
|
|
75
|
+
body,
|
|
76
|
+
metadata: headers
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async _request(serviceId, req) {
|
|
80
|
+
const pool = this.pools.get(serviceId);
|
|
81
|
+
if (!pool) throw new Error(`Unknown service: ${serviceId}`);
|
|
82
|
+
const svc = this.config.services[serviceId];
|
|
83
|
+
const method = req.metadata?.["http-method"] ?? (req.payload !== void 0 ? "POST" : "GET");
|
|
84
|
+
return pool.request({
|
|
85
|
+
method,
|
|
86
|
+
path: req.path,
|
|
87
|
+
headers: {
|
|
88
|
+
...req.payload !== void 0 ? { "content-type": "application/json" } : {},
|
|
89
|
+
...req.metadata
|
|
90
|
+
},
|
|
91
|
+
body: req.payload !== void 0 ? JSON.stringify(req.payload) : void 0,
|
|
92
|
+
headersTimeout: req.timeoutMs ?? svc.timeoutMs ?? 3e4,
|
|
93
|
+
signal: req.signal
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async health() {
|
|
97
|
+
const results = {};
|
|
98
|
+
await Promise.allSettled(
|
|
99
|
+
Object.keys(this.config.services).map(async (id) => {
|
|
100
|
+
try {
|
|
101
|
+
const res = await this.call(id, { path: "/health" });
|
|
102
|
+
results[id] = res.ok;
|
|
103
|
+
} catch {
|
|
104
|
+
results[id] = false;
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
const allOk = Object.values(results).every(Boolean);
|
|
109
|
+
return { status: allOk ? "ok" : "degraded", services: results };
|
|
110
|
+
}
|
|
111
|
+
/** Drain and close all connection pools. */
|
|
112
|
+
async destroy() {
|
|
113
|
+
await Promise.allSettled(
|
|
114
|
+
Array.from(this.pools.values()).map((pool) => pool.destroy())
|
|
115
|
+
);
|
|
116
|
+
this.pools.clear();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// src/manifest.ts
|
|
121
|
+
var manifest = {
|
|
122
|
+
manifestVersion: "1.0.0",
|
|
123
|
+
id: "service-transport-http",
|
|
124
|
+
name: "HTTP Service Transport",
|
|
125
|
+
version: "1.0.0",
|
|
126
|
+
description: "HTTP/unix-socket transport for gateway \u2192 service communication",
|
|
127
|
+
type: "core",
|
|
128
|
+
implements: "IServiceTransport"
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/index.ts
|
|
132
|
+
function createAdapter(config) {
|
|
133
|
+
return new HttpServiceTransport(config);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { HttpServiceTransport, applyPortOffset, createAdapter, manifest };
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
138
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transport.ts","../src/manifest.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { Pool } from 'undici';\nimport type {\n IServiceTransport,\n ServiceConnectionInfo,\n ServiceListenAddress,\n ServiceTransportRequest,\n ServiceTransportResponse,\n ServiceTransportStream,\n ServiceTransportHealth,\n} from '@kb-labs/sdk';\n\nexport interface HttpServiceTransportConfig {\n services: Record<string, {\n url: string;\n socketPath?: string;\n timeoutMs?: number;\n }>;\n /**\n * Per-environment TCP port shift, added to every service url's port. Sockets\n * are unaffected (they isolate via KB_SOCKET_HASH path, not port). This is a\n * LOCAL mechanism for running multiple environments on one host; in\n * cloud/k8s the offset is 0 and isolation comes from namespace/DNS. Falls\n * back to the KB_NET_OFFSET env var when not set in config.\n */\n offset?: number;\n}\n\n/**\n * Shift the port of a TCP url by offset, leaving scheme/host/path intact.\n * No-op for offset 0, urls without an explicit port (e.g. socket placeholders),\n * or unparseable urls. The single helper used for both route and bind so the\n * two never drift.\n */\nexport function applyPortOffset(url: string, offset: number): string {\n if (!offset) return url;\n try {\n const u = new URL(url);\n if (!u.port) return url;\n u.port = String(Number(u.port) + offset);\n return u.toString().replace(/\\/$/, '');\n } catch {\n return url;\n }\n}\n\nfunction assertResolvedSocketPath(socketPath: string, serviceId: string): void {\n if (/\\$\\{[^}]+\\}/.test(socketPath)) {\n throw new Error(\n `Service \"${serviceId}\" has an unresolved placeholder in socketPath: \"${socketPath}\". ` +\n `Check that the required environment variable is set (e.g. KB_SOCKET_HASH).`,\n );\n }\n}\n\nexport class HttpServiceTransport implements IServiceTransport {\n private readonly pools = new Map<string, Pool>();\n private readonly offset: number;\n\n constructor(private readonly config: HttpServiceTransportConfig) {\n this.offset = config.offset ?? (Number(process.env.KB_NET_OFFSET) || 0);\n for (const [id, svc] of Object.entries(config.services)) {\n // Socket services route via socketPath; their url is a placeholder, so\n // the offset is harmlessly skipped. TCP services get the shifted url.\n const base = svc.socketPath ? svc.url : applyPortOffset(svc.url, this.offset);\n this.pools.set(id, new Pool(base, {\n ...(svc.socketPath ? { socketPath: svc.socketPath } : {}),\n connections: 10,\n pipelining: 0,\n }));\n }\n }\n\n connectionInfo(serviceId: string): ServiceConnectionInfo | undefined {\n const svc = this.config.services[serviceId];\n if (!svc) return undefined;\n if (svc.socketPath) assertResolvedSocketPath(svc.socketPath, serviceId);\n const baseUrl = svc.socketPath ? svc.url : applyPortOffset(svc.url, this.offset);\n return { baseUrl, socketPath: svc.socketPath };\n }\n\n listenAddress(serviceId: string): ServiceListenAddress | undefined {\n const svc = this.config.services[serviceId];\n if (!svc) return undefined;\n if (svc.socketPath) {\n assertResolvedSocketPath(svc.socketPath, serviceId);\n return { socketPath: svc.socketPath };\n }\n // Same offset as the route, so bind and route ports never drift. Host is\n // omitted — the local adapter doesn't own the bind host; the daemon keeps\n // its own host config. Guard a url without an explicit port (would parse to\n // 0): return undefined so the caller falls back to its default port.\n let port: number;\n try {\n const u = new URL(applyPortOffset(svc.url, this.offset));\n if (!u.port) return undefined;\n port = Number(u.port);\n } catch {\n return undefined;\n }\n return { port };\n }\n\n async call(serviceId: string, req: ServiceTransportRequest): Promise<ServiceTransportResponse> {\n const { statusCode, headers, body } = await this._request(serviceId, req);\n const payload = await body.json().catch(async () => {\n const text = await body.text().catch(() => undefined);\n return text ?? undefined;\n });\n return { ok: statusCode < 400, statusCode, payload, metadata: headers as Record<string, string> };\n }\n\n async stream(serviceId: string, req: ServiceTransportRequest): Promise<ServiceTransportStream> {\n const { statusCode, headers, body } = await this._request(serviceId, req);\n return {\n ok: statusCode < 400,\n statusCode,\n body: body as unknown as AsyncIterable<Uint8Array>,\n metadata: headers as Record<string, string>,\n };\n }\n\n private async _request(serviceId: string, req: ServiceTransportRequest) {\n const pool = this.pools.get(serviceId);\n if (!pool) throw new Error(`Unknown service: ${serviceId}`);\n\n const svc = this.config.services[serviceId]!;\n const method = req.metadata?.['http-method'] ?? (req.payload !== undefined ? 'POST' : 'GET');\n\n return pool.request({\n method,\n path: req.path,\n headers: {\n ...(req.payload !== undefined ? { 'content-type': 'application/json' } : {}),\n ...req.metadata,\n },\n body: req.payload !== undefined ? JSON.stringify(req.payload) : undefined,\n headersTimeout: req.timeoutMs ?? svc.timeoutMs ?? 30_000,\n signal: req.signal,\n });\n }\n\n async health(): Promise<ServiceTransportHealth> {\n const results: Record<string, boolean> = {};\n await Promise.allSettled(\n Object.keys(this.config.services).map(async id => {\n try {\n const res = await this.call(id, { path: '/health' });\n results[id] = res.ok;\n } catch {\n results[id] = false;\n }\n }),\n );\n const allOk = Object.values(results).every(Boolean);\n return { status: allOk ? 'ok' : 'degraded', services: results };\n }\n\n /** Drain and close all connection pools. */\n async destroy(): Promise<void> {\n await Promise.allSettled(\n Array.from(this.pools.values()).map(pool => pool.destroy()),\n );\n this.pools.clear();\n }\n}\n","import type { AdapterManifest } from '@kb-labs/sdk/adapters';\n\nexport const manifest: AdapterManifest = {\n manifestVersion: '1.0.0',\n id: 'service-transport-http',\n name: 'HTTP Service Transport',\n version: '1.0.0',\n description: 'HTTP/unix-socket transport for gateway → service communication',\n type: 'core',\n implements: 'IServiceTransport',\n};\n","/**\n * @module @kb-labs/adapters-service-transport-http\n * HTTP/unix-socket implementation of IServiceTransport.\n *\n * Uses undici connection pools — supports both TCP and unix domain sockets.\n * Platform-only: never exposed to plugin context.\n */\n\nimport { HttpServiceTransport, type HttpServiceTransportConfig } from './transport.js';\n\nexport { HttpServiceTransport, type HttpServiceTransportConfig, applyPortOffset } from './transport.js';\nexport { manifest } from './manifest.js';\n\nexport function createAdapter(config: HttpServiceTransportConfig): HttpServiceTransport {\n return new HttpServiceTransport(config);\n}\n\nexport type {\n IServiceTransport,\n ServiceConnectionInfo,\n ServiceTransportRequest,\n ServiceTransportResponse,\n ServiceTransportStream,\n ServiceTransportHealth,\n} from '@kb-labs/sdk';\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/adapters-service-transport-http",
|
|
3
|
+
"description": "HTTP/unix-socket service transport implementing IServiceTransport",
|
|
4
|
+
"version": "2.96.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"undici": "^7"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^24.3.3",
|
|
23
|
+
"eslint": "^9",
|
|
24
|
+
"fastify": "^5.8.5",
|
|
25
|
+
"rimraf": "^6.0.1",
|
|
26
|
+
"tsup": "^8.5.0",
|
|
27
|
+
"typescript": "^5.6.3",
|
|
28
|
+
"vitest": "^3.2.6",
|
|
29
|
+
"@kb-labs/sdk": "2.21.2",
|
|
30
|
+
"@kb-labs/devkit": "2.96.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20.0.0",
|
|
34
|
+
"pnpm": ">=9.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@kb-labs/sdk": "^2.21.2"
|
|
38
|
+
},
|
|
39
|
+
"sideEffects": false,
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"clean": "rimraf dist",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"lint": "eslint src --ext .ts",
|
|
45
|
+
"lint:fix": "eslint . --fix",
|
|
46
|
+
"test": "vitest run --passWithNoTests",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"type-check": "tsc --noEmit"
|
|
49
|
+
}
|
|
50
|
+
}
|