@callsitehq/runtime 0.1.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/dist/index.d.ts +8 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AnyCapability } from '@callsitehq/core';
|
|
2
|
+
|
|
3
|
+
interface FetchHandlerOptions {
|
|
4
|
+
readonly basePath?: string;
|
|
5
|
+
}
|
|
6
|
+
declare function createFetchHandler(capabilities: readonly AnyCapability[], options?: FetchHandlerOptions): (request: Request) => Promise<Response>;
|
|
7
|
+
|
|
8
|
+
export { type FetchHandlerOptions, createFetchHandler };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
CapabilityError
|
|
4
|
+
} from "@callsitehq/core";
|
|
5
|
+
function createFetchHandler(capabilities, options = {}) {
|
|
6
|
+
const basePath = normalizeBasePath(options.basePath ?? "/capabilities");
|
|
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
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
createFetchHandler
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n CapabilityError,\n type AnyCapability,\n type CapabilityContext,\n type JsonValue\n} from \"@callsitehq/core\";\n\nexport interface FetchHandlerOptions {\n readonly basePath?: string;\n}\n\nexport function createFetchHandler(\n capabilities: readonly AnyCapability[],\n options: FetchHandlerOptions = {}\n): (request: Request) => Promise<Response> {\n const basePath = normalizeBasePath(options.basePath ?? \"/capabilities\");\n const byId = new Map(capabilities.map((capability) => [capability.id, capability]));\n\n return async function fetchHandler(request: Request): Promise<Response> {\n if (request.method !== \"POST\") {\n return json({ error: { code: \"method_not_allowed\", message: \"Use POST.\" } }, 405);\n }\n\n const id = capabilityIdFromRequest(request, basePath);\n if (id === undefined) {\n return json({ error: { code: \"not_found\", message: \"Capability route not found.\" } }, 404);\n }\n\n const capability = byId.get(id);\n if (capability === undefined) {\n return json({ error: { code: \"not_found\", message: `Capability \"${id}\" not found.` } }, 404);\n }\n\n try {\n const body = await request.json();\n const input = capability.input.parse(body);\n const context: CapabilityContext = { request };\n const result = await capability.run(input, context);\n const output = capability.output.parse(result);\n\n return json({ result: output }, 200);\n } catch (error) {\n return errorResponse(error);\n }\n };\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 return id.length === 0 ? undefined : decodeURIComponent(id);\n}\n\nfunction errorResponse(error: unknown): Response {\n if (error instanceof CapabilityError) {\n return json(\n {\n error: {\n code: error.code,\n message: error.message,\n ...(error.details === undefined ? {} : { details: error.details })\n }\n },\n statusForCapabilityError(error)\n );\n }\n\n if (error instanceof SyntaxError || error instanceof TypeError) {\n return json({ error: { code: \"bad_request\", message: error.message } }, 400);\n }\n\n return json({ error: { code: \"internal\", message: \"Internal capability error.\" } }, 500);\n}\n\nfunction statusForCapabilityError(error: CapabilityError): number {\n switch (error.code) {\n case \"bad_request\":\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 \"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: JsonValue, status: number): Response {\n return new Response(JSON.stringify(body), {\n headers: {\n \"content-type\": \"application/json\"\n },\n status\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@callsitehq/runtime",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Runtime dispatch engine for generated Callsite handlers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@callsitehq/core": "0.1.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
22
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
23
|
+
}
|
|
24
|
+
}
|