@callsitehq/runtime 0.1.0 → 0.2.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/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/aws-lambda.d.ts +9 -0
- package/dist/aws-lambda.js +280 -0
- package/dist/aws-lambda.js.map +1 -0
- package/dist/chunk-BT7K4T26.js +196 -0
- package/dist/chunk-BT7K4T26.js.map +1 -0
- package/dist/express.d.ts +10 -0
- package/dist/express.js +121 -0
- package/dist/express.js.map +1 -0
- package/dist/index.d.ts +30 -3
- package/dist/index.js +7 -87
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +15 -0
- package/dist/mcp.js +158 -0
- package/dist/mcp.js.map +1 -0
- package/dist/node.d.ts +7 -0
- package/dist/node.js +81 -0
- package/dist/node.js.map +1 -0
- package/package.json +54 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n CapabilityError,\n type AnyCapability,\n type AnyStandardSchema,\n type CapabilityContext,\n type CapabilityErrorCode,\n type JsonObject,\n type StandardIssue\n} from \"@callsitehq/core\";\n\nexport interface RuntimeManifest {\n readonly capabilities: Readonly<Record<string, AnyCapability>>;\n}\n\nexport interface RuntimeRequest {\n readonly capabilityId: string;\n readonly input: unknown;\n}\n\nexport type RuntimeResult = RuntimeSuccess | RuntimeFailure;\n\nexport interface RuntimeSuccess {\n readonly ok: true;\n readonly value: unknown;\n}\n\nexport interface RuntimeFailure {\n readonly ok: false;\n readonly error: RuntimeError;\n}\n\nexport interface RuntimeError {\n readonly code: CapabilityErrorCode;\n readonly message: string;\n readonly details?: JsonObject;\n}\n\nexport type RuntimeContext = Omit<CapabilityContext, \"log\"> &\n Partial<Pick<CapabilityContext, \"log\">>;\n\nexport type FetchContextProvider =\n RuntimeContext | ((request: Request) => RuntimeContext | Promise<RuntimeContext>);\n\nexport interface FetchHandlerOptions {\n readonly basePath?: string;\n readonly context?: FetchContextProvider;\n}\n\nexport type FetchHandler = (request: Request) => Promise<Response> | Response;\n\nexport function createRuntimeManifest(capabilities: readonly AnyCapability[]): RuntimeManifest {\n return {\n capabilities: Object.assign(\n Object.create(null) as Record<string, AnyCapability>,\n Object.fromEntries(capabilities.map((capability) => [capability.id, capability]))\n )\n };\n}\n\nexport async function execute(\n manifest: RuntimeManifest,\n request: RuntimeRequest,\n context: RuntimeContext = {}\n): Promise<RuntimeResult> {\n const capability = capabilityFromManifest(manifest, request.capabilityId);\n\n if (capability === undefined) {\n return failure(\"not_found\", `Capability \"${request.capabilityId}\" not found.`);\n }\n\n const inputResult = await validate(capability.input, request.input);\n if (!inputResult.ok) {\n return failure(\"invalid_input\", \"Invalid input.\", validationDetails(inputResult.issues));\n }\n\n try {\n const runResult = await capability.run(inputResult.value, capabilityContext(context));\n const outputResult = await validate(capability.output, runResult);\n\n if (!outputResult.ok) {\n return failure(\n \"internal\",\n \"Capability returned invalid output.\",\n validationDetails(outputResult.issues)\n );\n }\n\n return {\n ok: true,\n value: outputResult.value\n };\n } catch (error) {\n return failureFromThrown(error);\n }\n}\n\nfunction capabilityFromManifest(\n manifest: RuntimeManifest,\n capabilityId: string\n): AnyCapability | undefined {\n if (!Object.hasOwn(manifest.capabilities, capabilityId)) {\n return undefined;\n }\n\n return manifest.capabilities[capabilityId];\n}\n\nexport function createFetchHandler(\n manifestOrCapabilities: RuntimeManifest | readonly AnyCapability[],\n options: FetchHandlerOptions = {}\n): FetchHandler {\n const basePath = normalizeBasePath(options.basePath ?? \"/capabilities\");\n const manifest = isRuntimeManifest(manifestOrCapabilities)\n ? manifestOrCapabilities\n : createRuntimeManifest(manifestOrCapabilities);\n\n return async function fetchHandler(request: Request): Promise<Response> {\n if (request.method !== \"POST\") {\n return json({ error: runtimeError(\"invalid_input\", \"Use POST.\") }, 405);\n }\n\n const capabilityId = capabilityIdFromRequest(request, basePath);\n if (capabilityId === undefined) {\n return json({ error: runtimeError(\"not_found\", \"Capability route not found.\") }, 404);\n }\n\n const input = await requestJson(request);\n if (!input.ok) {\n return json({ error: input.error }, statusForErrorCode(input.error.code));\n }\n\n const result = await execute(\n manifest,\n { capabilityId, input: input.value },\n await contextFor(request, options.context)\n );\n\n if (!result.ok) {\n return json({ error: result.error }, statusForErrorCode(result.error.code));\n }\n\n return json(result.value, 200);\n };\n}\n\nfunction isRuntimeManifest(\n value: RuntimeManifest | readonly AnyCapability[]\n): value is RuntimeManifest {\n return !Array.isArray(value) && \"capabilities\" in value;\n}\n\ntype ValidationResult =\n | {\n readonly ok: true;\n readonly value: unknown;\n }\n | {\n readonly ok: false;\n readonly issues: readonly StandardIssue[];\n };\n\nasync function validate(schema: AnyStandardSchema, value: unknown): Promise<ValidationResult> {\n const result = await schema[\"~standard\"].validate(value);\n\n if (result.issues !== undefined) {\n return {\n ok: false,\n issues: result.issues\n };\n }\n\n return {\n ok: true,\n value: result.value\n };\n}\n\nfunction capabilityContext(context: RuntimeContext): CapabilityContext {\n return {\n ...(context.subject === undefined ? {} : { subject: context.subject }),\n log: context.log ?? noopLog\n };\n}\n\nfunction failure(code: CapabilityErrorCode, message: string, details?: JsonObject): RuntimeFailure {\n return {\n ok: false,\n error: runtimeError(code, message, details)\n };\n}\n\nfunction failureFromThrown(error: unknown): RuntimeFailure {\n if (error instanceof CapabilityError) {\n return failure(error.code, error.message, error.details);\n }\n\n return failure(\"internal\", \"Internal capability error.\");\n}\n\nfunction runtimeError(\n code: CapabilityErrorCode,\n message: string,\n details?: JsonObject\n): RuntimeError {\n return {\n code,\n message,\n ...(details === undefined ? {} : { details })\n };\n}\n\nfunction validationDetails(issues: readonly StandardIssue[]): JsonObject {\n return {\n issues: issues.map((issue) => ({\n message: issue.message,\n ...(issue.path === undefined ? {} : { path: issue.path.map(pathSegmentToJson) })\n }))\n };\n}\n\nfunction pathSegmentToJson(segment: NonNullable<StandardIssue[\"path\"]>[number]): string | number {\n const key =\n typeof segment === \"object\" && segment !== null && \"key\" in segment ? segment.key : segment;\n\n return typeof key === \"number\" ? key : String(key);\n}\n\nfunction capabilityIdFromRequest(request: Request, basePath: string): string | undefined {\n const { pathname } = new URL(request.url);\n\n if (!pathname.startsWith(`${basePath}/`)) {\n return undefined;\n }\n\n const id = pathname.slice(basePath.length + 1);\n if (id.length === 0) {\n return undefined;\n }\n\n try {\n return decodeURIComponent(id);\n } catch {\n return undefined;\n }\n}\n\nasync function requestJson(request: Request): Promise<RuntimeResult> {\n try {\n return {\n ok: true,\n value: await request.json()\n };\n } catch {\n return failure(\"invalid_input\", \"Request body must be valid JSON.\");\n }\n}\n\nasync function contextFor(\n request: Request,\n provider: FetchContextProvider | undefined\n): Promise<RuntimeContext> {\n if (provider === undefined) {\n return {};\n }\n\n return typeof provider === \"function\" ? provider(request) : provider;\n}\n\nfunction statusForErrorCode(code: CapabilityErrorCode): number {\n switch (code) {\n case \"invalid_input\":\n return 400;\n case \"unauthorized\":\n return 401;\n case \"forbidden\":\n return 403;\n case \"not_found\":\n return 404;\n case \"conflict\":\n return 409;\n case \"rate_limited\":\n return 429;\n case \"unavailable\":\n return 503;\n case \"internal\":\n return 500;\n }\n}\n\nfunction normalizeBasePath(basePath: string): string {\n const withLeadingSlash = basePath.startsWith(\"/\") ? basePath : `/${basePath}`;\n return withLeadingSlash.endsWith(\"/\") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;\n}\n\nfunction json(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n headers: {\n \"content-type\": \"application/json\"\n },\n status\n });\n}\n\nfunction noopLog(): void {}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RequestHandler, NextFunction, Request as Request$1, Response as Response$1 } from 'express';
|
|
2
|
+
|
|
3
|
+
type FetchHandler = (request: Request) => Promise<Response> | Response;
|
|
4
|
+
type ExpressRequest = Request$1;
|
|
5
|
+
type ExpressResponse = Response$1;
|
|
6
|
+
type ExpressNextFunction = NextFunction;
|
|
7
|
+
type ExpressHandler = RequestHandler;
|
|
8
|
+
declare function createExpressHandler(fetchHandler: FetchHandler): ExpressHandler;
|
|
9
|
+
|
|
10
|
+
export { type ExpressHandler, type ExpressNextFunction, type ExpressRequest, type ExpressResponse, createExpressHandler };
|
package/dist/express.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/express.ts
|
|
2
|
+
function createExpressHandler(fetchHandler) {
|
|
3
|
+
return (expressRequest, expressResponse, next) => {
|
|
4
|
+
void handleExpressRequest(fetchHandler, expressRequest, expressResponse).catch(next);
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
async function handleExpressRequest(fetchHandler, expressRequest, expressResponse) {
|
|
8
|
+
const response = await fetchHandler(await requestFromExpress(expressRequest));
|
|
9
|
+
await writeExpressResponse(expressResponse, response);
|
|
10
|
+
}
|
|
11
|
+
async function requestFromExpress(expressRequest) {
|
|
12
|
+
const headers = headersFromExpress(expressRequest.headers);
|
|
13
|
+
const method = expressRequest.method ?? "GET";
|
|
14
|
+
const init = {
|
|
15
|
+
headers,
|
|
16
|
+
method
|
|
17
|
+
};
|
|
18
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
19
|
+
const body = await bodyFromExpress(expressRequest);
|
|
20
|
+
if (body !== void 0) {
|
|
21
|
+
init.body = body.value;
|
|
22
|
+
if (body.contentType !== void 0) {
|
|
23
|
+
headers.set("content-type", body.contentType);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return new Request(urlFromExpress(expressRequest, headers), init);
|
|
28
|
+
}
|
|
29
|
+
function headersFromExpress(expressHeaders) {
|
|
30
|
+
const headers = new Headers();
|
|
31
|
+
for (const [name, value] of Object.entries(expressHeaders)) {
|
|
32
|
+
if (value === void 0) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
for (const item of value) {
|
|
37
|
+
headers.append(name, item);
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
headers.set(name, value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return headers;
|
|
44
|
+
}
|
|
45
|
+
async function bodyFromExpress(expressRequest) {
|
|
46
|
+
if ("body" in expressRequest && expressRequest.body !== void 0) {
|
|
47
|
+
return bodyFromParsedExpressBody(expressRequest.body);
|
|
48
|
+
}
|
|
49
|
+
if (!isAsyncIterable(expressRequest)) {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
value: await bodyFromStream(expressRequest)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function bodyFromParsedExpressBody(body) {
|
|
57
|
+
if (typeof body === "string") {
|
|
58
|
+
return { value: body };
|
|
59
|
+
}
|
|
60
|
+
if (body instanceof ArrayBuffer) {
|
|
61
|
+
return { value: body };
|
|
62
|
+
}
|
|
63
|
+
if (body instanceof Uint8Array) {
|
|
64
|
+
return { value: arrayBufferFromBytes(body) };
|
|
65
|
+
}
|
|
66
|
+
if (body instanceof URLSearchParams) {
|
|
67
|
+
return {
|
|
68
|
+
contentType: "application/x-www-form-urlencoded;charset=UTF-8",
|
|
69
|
+
value: body
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
contentType: "application/json",
|
|
74
|
+
value: JSON.stringify(body)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function arrayBufferFromBytes(bytes) {
|
|
78
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
79
|
+
}
|
|
80
|
+
async function bodyFromStream(stream) {
|
|
81
|
+
const chunks = [];
|
|
82
|
+
for await (const chunk of stream) {
|
|
83
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk));
|
|
84
|
+
}
|
|
85
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
86
|
+
}
|
|
87
|
+
function isAsyncIterable(value) {
|
|
88
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
89
|
+
}
|
|
90
|
+
function urlFromExpress(expressRequest, headers) {
|
|
91
|
+
const path = expressRequest.originalUrl ?? expressRequest.url ?? "/";
|
|
92
|
+
const protocol = expressRequest.protocol ?? "http";
|
|
93
|
+
const host = headers.get("host") ?? "localhost";
|
|
94
|
+
return new URL(path, `${protocol}://${host}`).toString();
|
|
95
|
+
}
|
|
96
|
+
async function writeExpressResponse(expressResponse, response) {
|
|
97
|
+
expressResponse.status(response.status);
|
|
98
|
+
const setCookie = getSetCookie(response.headers);
|
|
99
|
+
response.headers.forEach((value, name) => {
|
|
100
|
+
if (name.toLowerCase() === "set-cookie" && setCookie.length > 0) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
expressResponse.set(name, value);
|
|
104
|
+
});
|
|
105
|
+
if (setCookie.length > 0) {
|
|
106
|
+
expressResponse.set("set-cookie", [...setCookie]);
|
|
107
|
+
}
|
|
108
|
+
if (response.body === null) {
|
|
109
|
+
expressResponse.send();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
expressResponse.send(Buffer.from(await response.arrayBuffer()));
|
|
113
|
+
}
|
|
114
|
+
function getSetCookie(headers) {
|
|
115
|
+
const candidate = headers;
|
|
116
|
+
return candidate.getSetCookie?.() ?? [];
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
createExpressHandler
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/express.ts"],"sourcesContent":["import type {\n NextFunction as ExpressNextFunctionType,\n Request as ExpressRequestType,\n RequestHandler,\n Response as ExpressResponseType\n} from \"express\";\n\ntype FetchHandler = (request: Request) => Promise<Response> | Response;\n\nexport type ExpressRequest = ExpressRequestType;\nexport type ExpressResponse = ExpressResponseType;\nexport type ExpressNextFunction = ExpressNextFunctionType;\nexport type ExpressHandler = RequestHandler;\n\nexport function createExpressHandler(fetchHandler: FetchHandler): ExpressHandler {\n return (expressRequest, expressResponse, next) => {\n void handleExpressRequest(fetchHandler, expressRequest, expressResponse).catch(next);\n };\n}\n\nasync function handleExpressRequest(\n fetchHandler: FetchHandler,\n expressRequest: ExpressRequest,\n expressResponse: ExpressResponse\n): Promise<void> {\n const response = await fetchHandler(await requestFromExpress(expressRequest));\n await writeExpressResponse(expressResponse, response);\n}\n\nasync function requestFromExpress(expressRequest: ExpressRequest): Promise<Request> {\n const headers = headersFromExpress(expressRequest.headers);\n const method = expressRequest.method ?? \"GET\";\n const init: RequestInit = {\n headers,\n method\n };\n\n if (method !== \"GET\" && method !== \"HEAD\") {\n const body = await bodyFromExpress(expressRequest);\n if (body !== undefined) {\n init.body = body.value;\n if (body.contentType !== undefined) {\n headers.set(\"content-type\", body.contentType);\n }\n }\n }\n\n return new Request(urlFromExpress(expressRequest, headers), init);\n}\n\nfunction headersFromExpress(expressHeaders: ExpressRequest[\"headers\"]): Headers {\n const headers = new Headers();\n\n for (const [name, value] of Object.entries(expressHeaders)) {\n if (value === undefined) {\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n\n return headers;\n}\n\ninterface ExpressBody {\n readonly contentType?: string | undefined;\n readonly value: BodyInit;\n}\n\nasync function bodyFromExpress(expressRequest: ExpressRequest): Promise<ExpressBody | undefined> {\n if (\"body\" in expressRequest && expressRequest.body !== undefined) {\n return bodyFromParsedExpressBody(expressRequest.body);\n }\n\n if (!isAsyncIterable(expressRequest)) {\n return undefined;\n }\n\n return {\n value: await bodyFromStream(expressRequest)\n };\n}\n\nfunction bodyFromParsedExpressBody(body: unknown): ExpressBody {\n if (typeof body === \"string\") {\n return { value: body };\n }\n\n if (body instanceof ArrayBuffer) {\n return { value: body };\n }\n\n if (body instanceof Uint8Array) {\n return { value: arrayBufferFromBytes(body) };\n }\n\n if (body instanceof URLSearchParams) {\n return {\n contentType: \"application/x-www-form-urlencoded;charset=UTF-8\",\n value: body\n };\n }\n\n return {\n contentType: \"application/json\",\n value: JSON.stringify(body)\n };\n}\n\nfunction arrayBufferFromBytes(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nasync function bodyFromStream(stream: AsyncIterable<unknown>): Promise<string> {\n const chunks: Buffer[] = [];\n\n for await (const chunk of stream) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : Buffer.from(chunk as Uint8Array));\n }\n\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n\nfunction isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n Symbol.asyncIterator in value &&\n typeof value[Symbol.asyncIterator] === \"function\"\n );\n}\n\nfunction urlFromExpress(expressRequest: ExpressRequest, headers: Headers): string {\n const path = expressRequest.originalUrl ?? expressRequest.url ?? \"/\";\n const protocol = expressRequest.protocol ?? \"http\";\n const host = headers.get(\"host\") ?? \"localhost\";\n\n return new URL(path, `${protocol}://${host}`).toString();\n}\n\nasync function writeExpressResponse(\n expressResponse: ExpressResponse,\n response: Response\n): Promise<void> {\n expressResponse.status(response.status);\n\n const setCookie = getSetCookie(response.headers);\n response.headers.forEach((value, name) => {\n if (name.toLowerCase() === \"set-cookie\" && setCookie.length > 0) {\n return;\n }\n\n expressResponse.set(name, value);\n });\n\n if (setCookie.length > 0) {\n expressResponse.set(\"set-cookie\", [...setCookie]);\n }\n\n if (response.body === null) {\n expressResponse.send();\n return;\n }\n\n expressResponse.send(Buffer.from(await response.arrayBuffer()));\n}\n\nfunction getSetCookie(headers: Headers): readonly string[] {\n const candidate = headers as Headers & {\n getSetCookie?: () => string[];\n };\n\n return candidate.getSetCookie?.() ?? [];\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
|
-
import { AnyCapability } from '@callsitehq/core';
|
|
1
|
+
import { CapabilityContext, CapabilityErrorCode, JsonObject, AnyCapability } from '@callsitehq/core';
|
|
2
2
|
|
|
3
|
+
interface RuntimeManifest {
|
|
4
|
+
readonly capabilities: Readonly<Record<string, AnyCapability>>;
|
|
5
|
+
}
|
|
6
|
+
interface RuntimeRequest {
|
|
7
|
+
readonly capabilityId: string;
|
|
8
|
+
readonly input: unknown;
|
|
9
|
+
}
|
|
10
|
+
type RuntimeResult = RuntimeSuccess | RuntimeFailure;
|
|
11
|
+
interface RuntimeSuccess {
|
|
12
|
+
readonly ok: true;
|
|
13
|
+
readonly value: unknown;
|
|
14
|
+
}
|
|
15
|
+
interface RuntimeFailure {
|
|
16
|
+
readonly ok: false;
|
|
17
|
+
readonly error: RuntimeError;
|
|
18
|
+
}
|
|
19
|
+
interface RuntimeError {
|
|
20
|
+
readonly code: CapabilityErrorCode;
|
|
21
|
+
readonly message: string;
|
|
22
|
+
readonly details?: JsonObject;
|
|
23
|
+
}
|
|
24
|
+
type RuntimeContext = Omit<CapabilityContext, "log"> & Partial<Pick<CapabilityContext, "log">>;
|
|
25
|
+
type FetchContextProvider = RuntimeContext | ((request: Request) => RuntimeContext | Promise<RuntimeContext>);
|
|
3
26
|
interface FetchHandlerOptions {
|
|
4
27
|
readonly basePath?: string;
|
|
28
|
+
readonly context?: FetchContextProvider;
|
|
5
29
|
}
|
|
6
|
-
|
|
30
|
+
type FetchHandler = (request: Request) => Promise<Response> | Response;
|
|
31
|
+
declare function createRuntimeManifest(capabilities: readonly AnyCapability[]): RuntimeManifest;
|
|
32
|
+
declare function execute(manifest: RuntimeManifest, request: RuntimeRequest, context?: RuntimeContext): Promise<RuntimeResult>;
|
|
33
|
+
declare function createFetchHandler(manifestOrCapabilities: RuntimeManifest | readonly AnyCapability[], options?: FetchHandlerOptions): FetchHandler;
|
|
7
34
|
|
|
8
|
-
export { type FetchHandlerOptions, createFetchHandler };
|
|
35
|
+
export { type FetchContextProvider, type FetchHandler, type FetchHandlerOptions, type RuntimeContext, type RuntimeError, type RuntimeFailure, type RuntimeManifest, type RuntimeRequest, type RuntimeResult, type RuntimeSuccess, createFetchHandler, createRuntimeManifest, execute };
|
package/dist/index.js
CHANGED
|
@@ -1,91 +1,11 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const byId = new Map(capabilities.map((capability) => [capability.id, capability]));
|
|
8
|
-
return async function fetchHandler(request) {
|
|
9
|
-
if (request.method !== "POST") {
|
|
10
|
-
return json({ error: { code: "method_not_allowed", message: "Use POST." } }, 405);
|
|
11
|
-
}
|
|
12
|
-
const id = capabilityIdFromRequest(request, basePath);
|
|
13
|
-
if (id === void 0) {
|
|
14
|
-
return json({ error: { code: "not_found", message: "Capability route not found." } }, 404);
|
|
15
|
-
}
|
|
16
|
-
const capability = byId.get(id);
|
|
17
|
-
if (capability === void 0) {
|
|
18
|
-
return json({ error: { code: "not_found", message: `Capability "${id}" not found.` } }, 404);
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
const body = await request.json();
|
|
22
|
-
const input = capability.input.parse(body);
|
|
23
|
-
const context = { request };
|
|
24
|
-
const result = await capability.run(input, context);
|
|
25
|
-
const output = capability.output.parse(result);
|
|
26
|
-
return json({ result: output }, 200);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
return errorResponse(error);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
function capabilityIdFromRequest(request, basePath) {
|
|
33
|
-
const { pathname } = new URL(request.url);
|
|
34
|
-
if (!pathname.startsWith(`${basePath}/`)) {
|
|
35
|
-
return void 0;
|
|
36
|
-
}
|
|
37
|
-
const id = pathname.slice(basePath.length + 1);
|
|
38
|
-
return id.length === 0 ? void 0 : decodeURIComponent(id);
|
|
39
|
-
}
|
|
40
|
-
function errorResponse(error) {
|
|
41
|
-
if (error instanceof CapabilityError) {
|
|
42
|
-
return json(
|
|
43
|
-
{
|
|
44
|
-
error: {
|
|
45
|
-
code: error.code,
|
|
46
|
-
message: error.message,
|
|
47
|
-
...error.details === void 0 ? {} : { details: error.details }
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
statusForCapabilityError(error)
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
if (error instanceof SyntaxError || error instanceof TypeError) {
|
|
54
|
-
return json({ error: { code: "bad_request", message: error.message } }, 400);
|
|
55
|
-
}
|
|
56
|
-
return json({ error: { code: "internal", message: "Internal capability error." } }, 500);
|
|
57
|
-
}
|
|
58
|
-
function statusForCapabilityError(error) {
|
|
59
|
-
switch (error.code) {
|
|
60
|
-
case "bad_request":
|
|
61
|
-
return 400;
|
|
62
|
-
case "unauthorized":
|
|
63
|
-
return 401;
|
|
64
|
-
case "forbidden":
|
|
65
|
-
return 403;
|
|
66
|
-
case "not_found":
|
|
67
|
-
return 404;
|
|
68
|
-
case "conflict":
|
|
69
|
-
return 409;
|
|
70
|
-
case "rate_limited":
|
|
71
|
-
return 429;
|
|
72
|
-
case "internal":
|
|
73
|
-
return 500;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function normalizeBasePath(basePath) {
|
|
77
|
-
const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
|
|
78
|
-
return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
79
|
-
}
|
|
80
|
-
function json(body, status) {
|
|
81
|
-
return new Response(JSON.stringify(body), {
|
|
82
|
-
headers: {
|
|
83
|
-
"content-type": "application/json"
|
|
84
|
-
},
|
|
85
|
-
status
|
|
86
|
-
});
|
|
87
|
-
}
|
|
2
|
+
createFetchHandler,
|
|
3
|
+
createRuntimeManifest,
|
|
4
|
+
execute
|
|
5
|
+
} from "./chunk-BT7K4T26.js";
|
|
88
6
|
export {
|
|
89
|
-
createFetchHandler
|
|
7
|
+
createFetchHandler,
|
|
8
|
+
createRuntimeManifest,
|
|
9
|
+
execute
|
|
90
10
|
};
|
|
91
11
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ToJsonSchema, AnyCapability } from '@callsitehq/core';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
4
|
+
import { ServerRequest, ServerNotification } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { RuntimeContext } from './index.js';
|
|
6
|
+
|
|
7
|
+
type McpRequestExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
|
8
|
+
type McpContextProvider = RuntimeContext | ((extra: McpRequestExtra, capabilityId: string) => RuntimeContext | Promise<RuntimeContext>);
|
|
9
|
+
interface RegisterCallsiteToolsOptions {
|
|
10
|
+
readonly toJsonSchema: ToJsonSchema;
|
|
11
|
+
readonly context?: McpContextProvider;
|
|
12
|
+
}
|
|
13
|
+
declare function registerCallsiteTools(server: McpServer, capabilities: readonly AnyCapability[], options: RegisterCallsiteToolsOptions): void;
|
|
14
|
+
|
|
15
|
+
export { type McpContextProvider, type McpRequestExtra, type RegisterCallsiteToolsOptions, registerCallsiteTools };
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRuntimeManifest,
|
|
3
|
+
execute
|
|
4
|
+
} from "./chunk-BT7K4T26.js";
|
|
5
|
+
|
|
6
|
+
// src/mcp.ts
|
|
7
|
+
import { toIR } from "@callsitehq/core";
|
|
8
|
+
import { mcpToolsFromIR } from "@callsitehq/emit";
|
|
9
|
+
import {
|
|
10
|
+
CallToolRequestSchema,
|
|
11
|
+
ListToolsRequestSchema
|
|
12
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
function registerCallsiteTools(server, capabilities, options) {
|
|
14
|
+
const ir = toIR(capabilities, options.toJsonSchema);
|
|
15
|
+
const manifest = createRuntimeManifest(capabilities);
|
|
16
|
+
const tools = mcpToolsFromIR(ir);
|
|
17
|
+
if (tools.length === 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const capabilityIdByToolName = toolNameMap(tools, capabilities);
|
|
21
|
+
const callsiteToolNames = new Set(capabilityIdByToolName.keys());
|
|
22
|
+
const sdkHandlers = reserveSdkTools(server, tools);
|
|
23
|
+
server.server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
|
|
24
|
+
const sdkResult = await sdkListResult(sdkHandlers, request, extra);
|
|
25
|
+
return {
|
|
26
|
+
...sdkResult,
|
|
27
|
+
tools: [...sdkResult.tools.filter((tool) => !callsiteToolNames.has(tool.name)), ...tools]
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
31
|
+
const toolName = request.params.name;
|
|
32
|
+
const capabilityId = capabilityIdByToolName.get(toolName);
|
|
33
|
+
if (capabilityId === void 0) {
|
|
34
|
+
return await sdkHandlers.call(request, extra);
|
|
35
|
+
}
|
|
36
|
+
const result = await execute(
|
|
37
|
+
manifest,
|
|
38
|
+
{
|
|
39
|
+
capabilityId,
|
|
40
|
+
input: request.params.arguments ?? {}
|
|
41
|
+
},
|
|
42
|
+
await contextFor(options.context, extra, capabilityId)
|
|
43
|
+
);
|
|
44
|
+
return toolResult(result);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function toolNameMap(tools, capabilities) {
|
|
48
|
+
const result = /* @__PURE__ */ new Map();
|
|
49
|
+
tools.forEach((tool, index) => {
|
|
50
|
+
const capability = capabilities[index];
|
|
51
|
+
if (capability === void 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (result.has(tool.name)) {
|
|
55
|
+
throw new TypeError(`Duplicate MCP tool name "${tool.name}".`);
|
|
56
|
+
}
|
|
57
|
+
result.set(tool.name, capability.id);
|
|
58
|
+
});
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
function reserveSdkTools(server, tools) {
|
|
62
|
+
for (const tool of tools) {
|
|
63
|
+
server.registerTool(
|
|
64
|
+
tool.name,
|
|
65
|
+
{
|
|
66
|
+
...tool.title === void 0 ? {} : { title: tool.title },
|
|
67
|
+
...tool.description === void 0 ? {} : { description: tool.description },
|
|
68
|
+
...sdkToolAnnotations(tool),
|
|
69
|
+
...sdkToolMeta(tool)
|
|
70
|
+
},
|
|
71
|
+
() => toolErrorResult({
|
|
72
|
+
error: {
|
|
73
|
+
code: "internal",
|
|
74
|
+
message: "Callsite MCP tool was not routed through the Callsite runtime."
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
list: sdkRequestHandler(server, "tools/list"),
|
|
81
|
+
call: sdkRequestHandler(server, "tools/call")
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function sdkListResult(sdkHandlers, request, extra) {
|
|
85
|
+
return await sdkHandlers.list(request, extra);
|
|
86
|
+
}
|
|
87
|
+
function sdkRequestHandler(server, method) {
|
|
88
|
+
const internals = server.server;
|
|
89
|
+
const handler = internals._requestHandlers?.get(method);
|
|
90
|
+
if (handler === void 0) {
|
|
91
|
+
throw new TypeError(`The MCP SDK did not install a ${method} request handler.`);
|
|
92
|
+
}
|
|
93
|
+
return handler;
|
|
94
|
+
}
|
|
95
|
+
function sdkToolAnnotations(tool) {
|
|
96
|
+
if (tool.annotations === void 0) {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
return { annotations: tool.annotations };
|
|
100
|
+
}
|
|
101
|
+
function sdkToolMeta(tool) {
|
|
102
|
+
if (tool._meta === void 0) {
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
return { _meta: tool._meta };
|
|
106
|
+
}
|
|
107
|
+
async function contextFor(provider, extra, capabilityId) {
|
|
108
|
+
if (provider === void 0) {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
return typeof provider === "function" ? provider(extra, capabilityId) : provider;
|
|
112
|
+
}
|
|
113
|
+
function toolResult(result) {
|
|
114
|
+
if (!result.ok) {
|
|
115
|
+
return toolErrorResult({
|
|
116
|
+
error: result.error
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (!isJsonObject(result.value)) {
|
|
120
|
+
return toolErrorResult({
|
|
121
|
+
error: {
|
|
122
|
+
code: "internal",
|
|
123
|
+
message: "Capability returned non-object MCP output."
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: jsonText(result.value)
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
structuredContent: result.value,
|
|
135
|
+
isError: false
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function toolErrorResult(value) {
|
|
139
|
+
return {
|
|
140
|
+
content: [
|
|
141
|
+
{
|
|
142
|
+
type: "text",
|
|
143
|
+
text: jsonText(value)
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
isError: true
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function isJsonObject(value) {
|
|
150
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
151
|
+
}
|
|
152
|
+
function jsonText(value) {
|
|
153
|
+
return JSON.stringify(value) ?? String(value);
|
|
154
|
+
}
|
|
155
|
+
export {
|
|
156
|
+
registerCallsiteTools
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["import { toIR, type AnyCapability, type ToJsonSchema } from \"@callsitehq/core\";\nimport { mcpToolsFromIR } from \"@callsitehq/emit\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n type CallToolResult,\n type ListToolsResult,\n type ServerNotification,\n type ServerRequest,\n type ToolAnnotations\n} from \"@modelcontextprotocol/sdk/types.js\";\n\nimport {\n createRuntimeManifest,\n execute,\n type RuntimeContext,\n type RuntimeResult\n} from \"./index.js\";\n\nexport type McpRequestExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;\n\nexport type McpContextProvider =\n | RuntimeContext\n | ((extra: McpRequestExtra, capabilityId: string) => RuntimeContext | Promise<RuntimeContext>);\n\nexport interface RegisterCallsiteToolsOptions {\n readonly toJsonSchema: ToJsonSchema;\n readonly context?: McpContextProvider;\n}\n\ntype SdkRequestHandler = (request: unknown, extra: McpRequestExtra) => unknown | Promise<unknown>;\n\ninterface SdkProtocolInternals {\n readonly _requestHandlers?: Map<string, SdkRequestHandler>;\n}\n\nexport function registerCallsiteTools(\n server: McpServer,\n capabilities: readonly AnyCapability[],\n options: RegisterCallsiteToolsOptions\n): void {\n const ir = toIR(capabilities, options.toJsonSchema);\n const manifest = createRuntimeManifest(capabilities);\n const tools = mcpToolsFromIR(ir) as ListToolsResult[\"tools\"];\n if (tools.length === 0) {\n return;\n }\n\n const capabilityIdByToolName = toolNameMap(tools, capabilities);\n const callsiteToolNames = new Set(capabilityIdByToolName.keys());\n const sdkHandlers = reserveSdkTools(server, tools);\n\n server.server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {\n const sdkResult = await sdkListResult(sdkHandlers, request, extra);\n\n return {\n ...sdkResult,\n tools: [...sdkResult.tools.filter((tool) => !callsiteToolNames.has(tool.name)), ...tools]\n };\n });\n\n server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {\n const toolName = request.params.name;\n const capabilityId = capabilityIdByToolName.get(toolName);\n\n if (capabilityId === undefined) {\n return (await sdkHandlers.call(request, extra)) as CallToolResult;\n }\n\n const result = await execute(\n manifest,\n {\n capabilityId,\n input: request.params.arguments ?? {}\n },\n await contextFor(options.context, extra, capabilityId)\n );\n\n return toolResult(result);\n });\n}\n\nfunction toolNameMap(\n tools: readonly ListToolsResult[\"tools\"][number][],\n capabilities: readonly AnyCapability[]\n): ReadonlyMap<string, string> {\n const result = new Map<string, string>();\n\n tools.forEach((tool, index) => {\n const capability = capabilities[index];\n if (capability === undefined) {\n return;\n }\n\n if (result.has(tool.name)) {\n throw new TypeError(`Duplicate MCP tool name \"${tool.name}\".`);\n }\n\n result.set(tool.name, capability.id);\n });\n\n return result;\n}\n\nfunction reserveSdkTools(\n server: McpServer,\n tools: readonly ListToolsResult[\"tools\"][number][]\n): {\n readonly list: SdkRequestHandler;\n readonly call: SdkRequestHandler;\n} {\n // Reserve names in the SDK registry so host tools compose normally, then\n // wrap the SDK handlers to expose Callsite's already-lowered JSON Schema.\n for (const tool of tools) {\n server.registerTool(\n tool.name,\n {\n ...(tool.title === undefined ? {} : { title: tool.title }),\n ...(tool.description === undefined ? {} : { description: tool.description }),\n ...sdkToolAnnotations(tool),\n ...sdkToolMeta(tool)\n },\n () =>\n toolErrorResult({\n error: {\n code: \"internal\",\n message: \"Callsite MCP tool was not routed through the Callsite runtime.\"\n }\n })\n );\n }\n\n return {\n list: sdkRequestHandler(server, \"tools/list\"),\n call: sdkRequestHandler(server, \"tools/call\")\n };\n}\n\nasync function sdkListResult(\n sdkHandlers: {\n readonly list: SdkRequestHandler;\n },\n request: unknown,\n extra: McpRequestExtra\n): Promise<ListToolsResult> {\n return (await sdkHandlers.list(request, extra)) as ListToolsResult;\n}\n\nfunction sdkRequestHandler(server: McpServer, method: string): SdkRequestHandler {\n const internals = server.server as unknown as SdkProtocolInternals;\n const handler = internals._requestHandlers?.get(method);\n\n if (handler === undefined) {\n throw new TypeError(`The MCP SDK did not install a ${method} request handler.`);\n }\n\n return handler;\n}\n\nfunction sdkToolAnnotations(tool: ListToolsResult[\"tools\"][number]): {\n readonly annotations?: ToolAnnotations;\n} {\n if (tool.annotations === undefined) {\n return {};\n }\n\n return { annotations: tool.annotations as ToolAnnotations };\n}\n\nfunction sdkToolMeta(tool: ListToolsResult[\"tools\"][number]): {\n readonly _meta?: Record<string, unknown>;\n} {\n if (tool._meta === undefined) {\n return {};\n }\n\n return { _meta: tool._meta };\n}\n\nasync function contextFor(\n provider: McpContextProvider | undefined,\n extra: McpRequestExtra,\n capabilityId: string\n): Promise<RuntimeContext> {\n if (provider === undefined) {\n return {};\n }\n\n return typeof provider === \"function\" ? provider(extra, capabilityId) : provider;\n}\n\nfunction toolResult(result: RuntimeResult): CallToolResult {\n if (!result.ok) {\n return toolErrorResult({\n error: result.error\n });\n }\n\n if (!isJsonObject(result.value)) {\n return toolErrorResult({\n error: {\n code: \"internal\",\n message: \"Capability returned non-object MCP output.\"\n }\n });\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: jsonText(result.value)\n }\n ],\n structuredContent: result.value,\n isError: false\n };\n}\n\nfunction toolErrorResult(value: unknown): CallToolResult {\n return {\n content: [\n {\n type: \"text\",\n text: jsonText(value)\n }\n ],\n isError: true\n };\n}\n\nfunction isJsonObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction jsonText(value: unknown): string {\n return JSON.stringify(value) ?? String(value);\n}\n"],"mappings":"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","names":[]}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
|
|
3
|
+
type FetchHandler = (request: Request) => Promise<Response> | Response;
|
|
4
|
+
type NodeHandler = (request: IncomingMessage, response: ServerResponse) => void;
|
|
5
|
+
declare function createNodeHandler(fetchHandler: FetchHandler): NodeHandler;
|
|
6
|
+
|
|
7
|
+
export { type NodeHandler, createNodeHandler };
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/node.ts
|
|
2
|
+
function createNodeHandler(fetchHandler) {
|
|
3
|
+
return (nodeRequest, nodeResponse) => {
|
|
4
|
+
void handleNodeRequest(fetchHandler, nodeRequest, nodeResponse).catch(() => {
|
|
5
|
+
writeNodeError(nodeResponse);
|
|
6
|
+
});
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
async function handleNodeRequest(fetchHandler, nodeRequest, nodeResponse) {
|
|
10
|
+
const response = await fetchHandler(await requestFromNode(nodeRequest));
|
|
11
|
+
await writeNodeResponse(nodeResponse, response);
|
|
12
|
+
}
|
|
13
|
+
async function requestFromNode(nodeRequest) {
|
|
14
|
+
const headers = headersFromNode(nodeRequest);
|
|
15
|
+
const url = new URL(nodeRequest.url ?? "/", `http://${headers.get("host") ?? "localhost"}`);
|
|
16
|
+
const init = {
|
|
17
|
+
headers,
|
|
18
|
+
method: nodeRequest.method ?? "GET"
|
|
19
|
+
};
|
|
20
|
+
if (init.method !== "GET" && init.method !== "HEAD") {
|
|
21
|
+
init.body = await bodyFromNode(nodeRequest);
|
|
22
|
+
}
|
|
23
|
+
return new Request(url, init);
|
|
24
|
+
}
|
|
25
|
+
function headersFromNode(nodeRequest) {
|
|
26
|
+
const headers = new Headers();
|
|
27
|
+
for (const [name, value] of Object.entries(nodeRequest.headers)) {
|
|
28
|
+
if (value === void 0) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
for (const item of value) {
|
|
33
|
+
headers.append(name, item);
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
headers.set(name, value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return headers;
|
|
40
|
+
}
|
|
41
|
+
async function bodyFromNode(nodeRequest) {
|
|
42
|
+
const chunks = [];
|
|
43
|
+
for await (const chunk of nodeRequest) {
|
|
44
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk));
|
|
45
|
+
}
|
|
46
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
47
|
+
}
|
|
48
|
+
async function writeNodeResponse(nodeResponse, response) {
|
|
49
|
+
nodeResponse.statusCode = response.status;
|
|
50
|
+
if (response.statusText.length > 0) {
|
|
51
|
+
nodeResponse.statusMessage = response.statusText;
|
|
52
|
+
}
|
|
53
|
+
response.headers.forEach((value, name) => {
|
|
54
|
+
nodeResponse.setHeader(name, value);
|
|
55
|
+
});
|
|
56
|
+
if (response.body === null) {
|
|
57
|
+
nodeResponse.end();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
nodeResponse.end(Buffer.from(await response.arrayBuffer()));
|
|
61
|
+
}
|
|
62
|
+
function writeNodeError(nodeResponse) {
|
|
63
|
+
if (nodeResponse.headersSent) {
|
|
64
|
+
nodeResponse.destroy();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
nodeResponse.statusCode = 500;
|
|
68
|
+
nodeResponse.setHeader("content-type", "application/json");
|
|
69
|
+
nodeResponse.end(
|
|
70
|
+
JSON.stringify({
|
|
71
|
+
error: {
|
|
72
|
+
code: "internal",
|
|
73
|
+
message: "Internal server error."
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
createNodeHandler
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\n\ntype FetchHandler = (request: Request) => Promise<Response> | Response;\n\nexport type NodeHandler = (request: IncomingMessage, response: ServerResponse) => void;\n\nexport function createNodeHandler(fetchHandler: FetchHandler): NodeHandler {\n return (nodeRequest, nodeResponse) => {\n void handleNodeRequest(fetchHandler, nodeRequest, nodeResponse).catch(() => {\n writeNodeError(nodeResponse);\n });\n };\n}\n\nasync function handleNodeRequest(\n fetchHandler: FetchHandler,\n nodeRequest: IncomingMessage,\n nodeResponse: ServerResponse\n): Promise<void> {\n const response = await fetchHandler(await requestFromNode(nodeRequest));\n await writeNodeResponse(nodeResponse, response);\n}\n\nasync function requestFromNode(nodeRequest: IncomingMessage): Promise<Request> {\n const headers = headersFromNode(nodeRequest);\n const url = new URL(nodeRequest.url ?? \"/\", `http://${headers.get(\"host\") ?? \"localhost\"}`);\n const init: RequestInit = {\n headers,\n method: nodeRequest.method ?? \"GET\"\n };\n\n if (init.method !== \"GET\" && init.method !== \"HEAD\") {\n init.body = await bodyFromNode(nodeRequest);\n }\n\n return new Request(url, init);\n}\n\nfunction headersFromNode(nodeRequest: IncomingMessage): Headers {\n const headers = new Headers();\n\n for (const [name, value] of Object.entries(nodeRequest.headers)) {\n if (value === undefined) {\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n\n return headers;\n}\n\nasync function bodyFromNode(nodeRequest: IncomingMessage): Promise<string> {\n const chunks: Buffer[] = [];\n\n for await (const chunk of nodeRequest) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : Buffer.from(chunk));\n }\n\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n\nasync function writeNodeResponse(nodeResponse: ServerResponse, response: Response): Promise<void> {\n nodeResponse.statusCode = response.status;\n\n if (response.statusText.length > 0) {\n nodeResponse.statusMessage = response.statusText;\n }\n\n response.headers.forEach((value, name) => {\n nodeResponse.setHeader(name, value);\n });\n\n if (response.body === null) {\n nodeResponse.end();\n return;\n }\n\n nodeResponse.end(Buffer.from(await response.arrayBuffer()));\n}\n\nfunction writeNodeError(nodeResponse: ServerResponse): void {\n if (nodeResponse.headersSent) {\n nodeResponse.destroy();\n return;\n }\n\n nodeResponse.statusCode = 500;\n nodeResponse.setHeader(\"content-type\", \"application/json\");\n nodeResponse.end(\n JSON.stringify({\n error: {\n code: \"internal\",\n message: \"Internal server error.\"\n }\n })\n );\n}\n"],"mappings":";AAMO,SAAS,kBAAkB,cAAyC;AACzE,SAAO,CAAC,aAAa,iBAAiB;AACpC,SAAK,kBAAkB,cAAc,aAAa,YAAY,EAAE,MAAM,MAAM;AAC1E,qBAAe,YAAY;AAAA,IAC7B,CAAC;AAAA,EACH;AACF;AAEA,eAAe,kBACb,cACA,aACA,cACe;AACf,QAAM,WAAW,MAAM,aAAa,MAAM,gBAAgB,WAAW,CAAC;AACtE,QAAM,kBAAkB,cAAc,QAAQ;AAChD;AAEA,eAAe,gBAAgB,aAAgD;AAC7E,QAAM,UAAU,gBAAgB,WAAW;AAC3C,QAAM,MAAM,IAAI,IAAI,YAAY,OAAO,KAAK,UAAU,QAAQ,IAAI,MAAM,KAAK,WAAW,EAAE;AAC1F,QAAM,OAAoB;AAAA,IACxB;AAAA,IACA,QAAQ,YAAY,UAAU;AAAA,EAChC;AAEA,MAAI,KAAK,WAAW,SAAS,KAAK,WAAW,QAAQ;AACnD,SAAK,OAAO,MAAM,aAAa,WAAW;AAAA,EAC5C;AAEA,SAAO,IAAI,QAAQ,KAAK,IAAI;AAC9B;AAEA,SAAS,gBAAgB,aAAuC;AAC9D,QAAM,UAAU,IAAI,QAAQ;AAE5B,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,YAAY,OAAO,GAAG;AAC/D,QAAI,UAAU,QAAW;AACvB;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,iBAAW,QAAQ,OAAO;AACxB,gBAAQ,OAAO,MAAM,IAAI;AAAA,MAC3B;AAAA,IACF,OAAO;AACL,cAAQ,IAAI,MAAM,KAAK;AAAA,IACzB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,aAAa,aAA+C;AACzE,QAAM,SAAmB,CAAC;AAE1B,mBAAiB,SAAS,aAAa;AACrC,WAAO,KAAK,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,KAAK,CAAC;AAAA,EACjF;AAEA,SAAO,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AAC9C;AAEA,eAAe,kBAAkB,cAA8B,UAAmC;AAChG,eAAa,aAAa,SAAS;AAEnC,MAAI,SAAS,WAAW,SAAS,GAAG;AAClC,iBAAa,gBAAgB,SAAS;AAAA,EACxC;AAEA,WAAS,QAAQ,QAAQ,CAAC,OAAO,SAAS;AACxC,iBAAa,UAAU,MAAM,KAAK;AAAA,EACpC,CAAC;AAED,MAAI,SAAS,SAAS,MAAM;AAC1B,iBAAa,IAAI;AACjB;AAAA,EACF;AAEA,eAAa,IAAI,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC;AAC5D;AAEA,SAAS,eAAe,cAAoC;AAC1D,MAAI,aAAa,aAAa;AAC5B,iBAAa,QAAQ;AACrB;AAAA,EACF;AAEA,eAAa,aAAa;AAC1B,eAAa,UAAU,gBAAgB,kBAAkB;AACzD,eAAa;AAAA,IACX,KAAK,UAAU;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|