Initial package implementation: operations registry, call protocol, and adapters

Extracted from alkhub_ts packages/core/operations/ and packages/core/mcp/.
- Runtime-agnostic (injected fs/env deps, no Deno globals)
- Direct @logtape/logtape import instead of logger wrapper
- PendingRequestMap with pubsub-wired call protocol
- Peer-dep isolation for MCP adapter (sub-path export)
- Schema const naming convention (XSchema + X type alias)
- 68 tests passing, build + lint + test all green
This commit is contained in:
2026-04-30 12:34:26 +00:00
parent 9c41f683ee
commit 29f0dd7af0
37 changed files with 9287 additions and 0 deletions

87
test/call.test.ts Normal file
View File

@@ -0,0 +1,87 @@
import { describe, it, expect } from "vitest";
import { PendingRequestMap } from "../src/call.js";
import { CallError, InfrastructureErrorCode } from "../src/error.js";
describe("PendingRequestMap", () => {
it("creates instance without event target", () => {
const map = new PendingRequestMap();
expect(map.getPendingCount()).toBe(0);
});
it("creates instance with event target", () => {
const target = new EventTarget();
const map = new PendingRequestMap(target);
expect(map.getPendingCount()).toBe(0);
});
it("call() resolves when respond() is called", async () => {
const map = new PendingRequestMap();
const callPromise = map.call("test.op", { value: "hello" });
setTimeout(() => {
const requestId = [...map["requests"].keys()][0];
map.respond(requestId, { result: "world" });
}, 10);
const result = await callPromise;
expect(result).toEqual({ result: "world" });
});
it("call() rejects when emitError() is called", async () => {
const map = new PendingRequestMap();
const callPromise = map.call("test.op", { value: "hello" });
setTimeout(() => {
const requestId = [...map["requests"].keys()][0];
map.emitError(requestId, "CUSTOM_ERROR", "Something went wrong");
}, 10);
await expect(callPromise).rejects.toThrow("Something went wrong");
await expect(callPromise).rejects.toBeInstanceOf(CallError);
});
it("abort() rejects the pending call", async () => {
const map = new PendingRequestMap();
const callPromise = map.call("test.op", { value: "hello" });
setTimeout(() => {
const requestId = [...map["requests"].keys()][0];
map.abort(requestId);
}, 10);
await expect(callPromise).rejects.toThrow("was aborted");
await expect(callPromise).rejects.toBeInstanceOf(CallError);
});
it("call() with deadline times out", async () => {
const map = new PendingRequestMap();
const deadline = Date.now() + 50;
const callPromise = map.call("test.op", { value: "hello" }, { deadline });
await expect(callPromise).rejects.toThrow("timed out");
await expect(callPromise).rejects.toBeInstanceOf(CallError);
});
it("tracks pending requests", () => {
const map = new PendingRequestMap();
map.call("test.op1", {});
map.call("test.op2", {});
expect(map.getPendingCount()).toBe(2);
});
it("cleans up after call resolves", async () => {
const map = new PendingRequestMap();
const callPromise = map.call("test.op", { value: "hello" });
expect(map.getPendingCount()).toBe(1);
const requestId = [...map["requests"].keys()][0];
map.respond(requestId, { result: "done" });
await callPromise;
expect(map.getPendingCount()).toBe(0);
});
});

93
test/env.test.ts Normal file
View File

@@ -0,0 +1,93 @@
import { describe, it, expect } from "vitest";
import { OperationRegistry, OperationType, buildEnv, type IOperationDefinition, type OperationContext } from "../src/index.js";
import * as Type from "@alkdev/typebox";
import { PendingRequestMap } from "../src/call.js";
function makeOperation(name: string, handler?: any): IOperationDefinition {
return {
name,
namespace: "test",
version: "1.0.0",
type: OperationType.QUERY,
description: `Test ${name}`,
inputSchema: Type.Object({ value: Type.String() }),
outputSchema: Type.Object({ result: Type.String() }),
accessControl: { requiredScopes: [] },
handler: handler || (async (input: any) => ({ result: input.value })),
};
}
describe("buildEnv", () => {
it("creates namespace-keyed env in direct mode", async () => {
const registry = new OperationRegistry();
registry.register(makeOperation("readFile"));
registry.register(makeOperation("writeFile"));
const env = buildEnv({
registry,
context: {} as OperationContext,
});
expect(env.test).toBeDefined();
expect(typeof env.test.readFile).toBe("function");
expect(typeof env.test.writeFile).toBe("function");
const result = await env.test.readFile({ value: "test" });
expect(result).toEqual({ result: "test" });
});
it("filters out SUBSCRIPTION operations", () => {
const registry = new OperationRegistry();
registry.register(makeOperation("query"));
registry.register({
...makeOperation("onEvent"),
type: OperationType.SUBSCRIPTION,
});
const env = buildEnv({
registry,
context: {} as OperationContext,
});
expect(env.test.query).toBeDefined();
expect(env.test.onEvent).toBeUndefined();
});
it("filters by allowedNamespaces", () => {
const registry = new OperationRegistry();
registry.register(makeOperation("op1"));
registry.register({
...makeOperation("op2"),
namespace: "other",
});
const env = buildEnv({
registry,
context: {} as OperationContext,
allowedNamespaces: ["test"],
});
expect(env.test).toBeDefined();
expect(env.other).toBeUndefined();
});
it("routes through callMap in call protocol mode", async () => {
const registry = new OperationRegistry();
registry.register(makeOperation("readFile"));
const callMap = {
call: async (opId: string, input: unknown, opts?: any) => {
return { result: `routed: ${opId}` };
},
};
const env = buildEnv({
registry,
context: {} as OperationContext,
callMap,
});
const result = await env.test.readFile({ value: "test" });
expect(result).toEqual({ result: "routed: test.readFile" });
});
});

65
test/error.test.ts Normal file
View File

@@ -0,0 +1,65 @@
import { describe, it, expect } from "vitest";
import { CallError, InfrastructureErrorCode, mapError } from "../src/error.js";
describe("CallError", () => {
it("stores code, message, and details", () => {
const err = new CallError("TEST_CODE", "test message", { foo: "bar" });
expect(err.code).toBe("TEST_CODE");
expect(err.message).toBe("test message");
expect(err.details).toEqual({ foo: "bar" });
expect(err.name).toBe("CallError");
expect(err).toBeInstanceOf(Error);
expect(err).toBeInstanceOf(CallError);
});
it("works without details", () => {
const err = new CallError("CODE", "msg");
expect(err.details).toBeUndefined();
});
});
describe("InfrastructureErrorCode", () => {
it("has all expected codes", () => {
expect(InfrastructureErrorCode.OPERATION_NOT_FOUND).toBe("OPERATION_NOT_FOUND");
expect(InfrastructureErrorCode.ACCESS_DENIED).toBe("ACCESS_DENIED");
expect(InfrastructureErrorCode.VALIDATION_ERROR).toBe("VALIDATION_ERROR");
expect(InfrastructureErrorCode.TIMEOUT).toBe("TIMEOUT");
expect(InfrastructureErrorCode.ABORTED).toBe("ABORTED");
expect(InfrastructureErrorCode.EXECUTION_ERROR).toBe("EXECUTION_ERROR");
expect(InfrastructureErrorCode.UNKNOWN_ERROR).toBe("UNKNOWN_ERROR");
});
});
describe("mapError", () => {
it("passes through existing CallError", () => {
const original = new CallError("CUSTOM", "msg");
const result = mapError(original);
expect(result).toBe(original);
});
it("maps Error to EXECUTION_ERROR", () => {
const result = mapError(new Error("something broke"));
expect(result.code).toBe(InfrastructureErrorCode.EXECUTION_ERROR);
expect(result.message).toBe("something broke");
});
it("maps Error with matching errorSchema code", () => {
const result = mapError(new Error("NOT_FOUND: item missing"), [
{ code: "NOT_FOUND", schema: {} },
]);
expect(result.code).toBe("NOT_FOUND");
});
it("maps non-Error to UNKNOWN_ERROR", () => {
const result = mapError("string error");
expect(result.code).toBe(InfrastructureErrorCode.UNKNOWN_ERROR);
expect(result.message).toBe("string error");
expect(result.details).toEqual({ raw: "string error" });
});
it("maps non-Error with details", () => {
const result = mapError(42);
expect(result.code).toBe(InfrastructureErrorCode.UNKNOWN_ERROR);
expect(result.details).toEqual({ raw: "42" });
});
});

194
test/from_openapi.test.ts Normal file
View File

@@ -0,0 +1,194 @@
import { describe, it, expect } from "vitest";
import { FromOpenAPI } from "../src/from_openapi.js";
import { OperationType } from "../src/types.js";
import { Value } from "@alkdev/typebox/value";
const simpleSpec = {
openapi: "3.0.0",
info: { title: "Test API", version: "1.0.0" },
paths: {
"/users": {
get: {
operationId: "listUsers",
description: "List all users",
responses: {
"200": {
content: {
"application/json": {
schema: {
type: "object",
properties: {
users: { type: "array", items: { type: "string" } },
},
},
},
},
},
},
},
post: {
operationId: "createUser",
description: "Create a user",
requestBody: {
content: {
"application/json": {
schema: {
type: "object",
properties: {
name: { type: "string" },
},
required: ["name"],
},
},
},
},
responses: {
"201": {
content: {
"application/json": {
schema: {
type: "object",
properties: {
id: { type: "string" },
},
},
},
},
},
},
},
},
"/events": {
get: {
operationId: "streamEvents",
description: "Stream events via SSE",
responses: {
"200": {
content: {
"text/event-stream": {
schema: {
type: "object",
properties: {
event: { type: "string" },
},
},
},
},
},
},
},
},
"/users/{id}": {
get: {
operationId: "getUser",
description: "Get user by ID",
parameters: [
{ name: "id", in: "path", required: true, schema: { type: "string" } },
],
responses: {
"200": {
content: {
"application/json": {
schema: { type: "object", properties: { name: { type: "string" } } },
},
},
},
},
},
},
},
};
describe("FromOpenAPI", () => {
const config = {
namespace: "api",
baseUrl: "https://api.example.com",
};
it("generates operations from OpenAPI spec", () => {
const ops = FromOpenAPI(simpleSpec as any, config);
expect(ops.length).toBeGreaterThan(0);
expect(ops.map((o) => o.name)).toContain("listUsers");
expect(ops.map((o) => o.name)).toContain("createUser");
expect(ops.map((o) => o.name)).toContain("getUser");
});
it("sets namespace from config", () => {
const ops = FromOpenAPI(simpleSpec as any, config);
expect(ops.every((o) => o.namespace === "api")).toBe(true);
});
it("detects GET as QUERY type", () => {
const ops = FromOpenAPI(simpleSpec as any, config);
const listUsers = ops.find((o) => o.name === "listUsers")!;
expect(listUsers.type).toBe(OperationType.QUERY);
});
it("detects POST as MUTATION type", () => {
const ops = FromOpenAPI(simpleSpec as any, config);
const createUser = ops.find((o) => o.name === "createUser")!;
expect(createUser.type).toBe(OperationType.MUTATION);
});
it("detects text/event-stream as SUBSCRIPTION type", () => {
const ops = FromOpenAPI(simpleSpec as any, config);
const streamEvents = ops.find((o) => o.name === "streamEvents")!;
expect(streamEvents.type).toBe(OperationType.SUBSCRIPTION);
});
it("generates valid TypeBox input schemas", () => {
const ops = FromOpenAPI(simpleSpec as any, config);
const getUser = ops.find((o) => o.name === "getUser")!;
expect(Value.Check(getUser.inputSchema, { id: "123" })).toBe(true);
});
it("handles auth bearer config", () => {
const authConfig = {
namespace: "api",
baseUrl: "https://api.example.com",
auth: { type: "bearer" as const, token: "test-token" },
};
const ops = FromOpenAPI(simpleSpec as any, authConfig);
expect(ops.length).toBeGreaterThan(0);
});
it("skips non-HTTP methods", () => {
const ops = FromOpenAPI(simpleSpec as any, config);
expect(ops.every((o) => o.name)).toBeTruthy();
});
it("handles $ref resolution", () => {
const specWithRef = {
openapi: "3.0.0",
info: { title: "Test", version: "1.0.0" },
paths: {
"/items": {
get: {
operationId: "listItems",
responses: {
"200": {
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ItemList" },
},
},
},
},
},
},
},
components: {
schemas: {
ItemList: {
type: "object",
properties: {
items: { type: "array", items: { type: "string" } },
},
},
},
},
};
const ops = FromOpenAPI(specWithRef as any, config);
expect(ops.length).toBe(1);
});
});

110
test/from_schema.test.ts Normal file
View File

@@ -0,0 +1,110 @@
import { describe, it, expect } from "vitest";
import { FromSchema } from "../src/from_schema.js";
import * as Type from "@alkdev/typebox";
import { KindGuard } from "@alkdev/typebox";
import { Value } from "@alkdev/typebox/value";
describe("FromSchema", () => {
it("converts a simple object schema", () => {
const jsonSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name"],
};
const tbox = FromSchema(jsonSchema);
expect(KindGuard.IsSchema(tbox)).toBe(true);
expect(Value.Check(tbox, { name: "Alice" })).toBe(true);
expect(Value.Check(tbox, { name: "Alice", age: 30 })).toBe(true);
});
it("converts string schema", () => {
const tbox = FromSchema({ type: "string" });
expect(KindGuard.IsSchema(tbox)).toBe(true);
expect(Value.Check(tbox, "hello")).toBe(true);
});
it("converts number schema", () => {
const tbox = FromSchema({ type: "number" });
expect(Value.Check(tbox, 42)).toBe(true);
});
it("converts integer schema", () => {
const tbox = FromSchema({ type: "integer" });
expect(Value.Check(tbox, 1)).toBe(true);
});
it("converts boolean schema", () => {
const tbox = FromSchema({ type: "boolean" });
expect(Value.Check(tbox, true)).toBe(true);
});
it("converts null schema", () => {
const tbox = FromSchema({ type: "null" });
expect(Value.Check(tbox, null)).toBe(true);
});
it("converts enum schema", () => {
const tbox = FromSchema({ enum: ["a", "b", "c"] });
expect(Value.Check(tbox, "a")).toBe(true);
expect(Value.Check(tbox, "d")).toBe(false);
});
it("converts array schema", () => {
const tbox = FromSchema({ type: "array", items: { type: "string" } });
expect(Value.Check(tbox, ["a", "b"])).toBe(true);
});
it("converts tuple schema", () => {
const tbox = FromSchema({ type: "array", items: [{ type: "string" }, { type: "number" }] });
expect(Value.Check(tbox, ["a", 1])).toBe(true);
});
it("converts allOf schema", () => {
const tbox = FromSchema({
allOf: [
{ type: "object", properties: { name: { type: "string" } }, required: ["name"] },
{ type: "object", properties: { age: { type: "number" } }, required: ["age"] },
],
});
expect(Value.Check(tbox, { name: "A", age: 1 })).toBe(true);
});
it("converts anyOf schema", () => {
const tbox = FromSchema({
anyOf: [{ type: "string" }, { type: "number" }],
});
expect(Value.Check(tbox, "hello")).toBe(true);
expect(Value.Check(tbox, 42)).toBe(true);
});
it("converts oneOf schema", () => {
const tbox = FromSchema({
oneOf: [{ type: "string" }, { type: "number" }],
});
expect(Value.Check(tbox, "hello")).toBe(true);
expect(Value.Check(tbox, 42)).toBe(true);
});
it("converts const schema with object value", () => {
const tbox = FromSchema({ const: { key: "value" } });
expect(KindGuard.IsSchema(tbox)).toBe(true);
});
it("converts primitive const as literal (falls through to Unknown for non-object const)", () => {
const tbox = FromSchema({ const: "fixed" });
expect(KindGuard.IsSchema(tbox)).toBe(true);
});
it("converts $ref schema", () => {
const tbox = FromSchema({ $ref: "#/definitions/MyType" });
expect(KindGuard.IsSchema(tbox)).toBe(true);
});
it("returns Unknown for unrecognized schemas", () => {
const tbox = FromSchema({});
expect(KindGuard.IsSchema(tbox)).toBe(true);
});
});

101
test/registry.test.ts Normal file
View File

@@ -0,0 +1,101 @@
import { describe, it, expect } from "vitest";
import { OperationRegistry } from "../src/registry.js";
import { OperationType, type IOperationDefinition, type OperationContext } from "../src/index.js";
import * as Type from "@alkdev/typebox";
import { Value } from "@alkdev/typebox/value";
function makeOperation(overrides: Partial<IOperationDefinition> = {}): IOperationDefinition {
return {
name: "testOp",
namespace: "test",
version: "1.0.0",
type: OperationType.QUERY,
description: "A test operation",
inputSchema: Type.Object({ value: Type.String() }),
outputSchema: Type.Object({ result: Type.String() }),
accessControl: { requiredScopes: [] },
handler: async (input: any) => ({ result: `processed: ${input.value}` }),
...overrides,
};
}
describe("OperationRegistry", () => {
it("registers and retrieves an operation", () => {
const registry = new OperationRegistry();
const op = makeOperation();
registry.register(op);
expect(registry.get("test.testOp")).toBe(op);
});
it("retrieves by namespace and name", () => {
const registry = new OperationRegistry();
const op = makeOperation();
registry.register(op);
expect(registry.getByName("test", "testOp")).toBe(op);
});
it("returns undefined for missing operations", () => {
const registry = new OperationRegistry();
expect(registry.get("nonexistent.op")).toBeUndefined();
});
it("lists all registered operations", () => {
const registry = new OperationRegistry();
registry.register(makeOperation());
registry.register(makeOperation({ name: "op2" }));
expect(registry.list()).toHaveLength(2);
});
it("registerAll registers multiple operations", () => {
const registry = new OperationRegistry();
registry.registerAll([makeOperation(), makeOperation({ name: "op2" })]);
expect(registry.list()).toHaveLength(2);
});
it("extracts spec without handler", () => {
const registry = new OperationRegistry();
registry.register(makeOperation());
const spec = registry.getSpec("test.testOp")!;
expect(spec).toBeDefined();
expect((spec as any).handler).toBeUndefined();
expect(spec.name).toBe("testOp");
});
it("getAllSpecs returns all specs", () => {
const registry = new OperationRegistry();
registry.register(makeOperation());
registry.register(makeOperation({ name: "op2" }));
expect(registry.getAllSpecs()).toHaveLength(2);
});
it("executes an operation and validates input", async () => {
const registry = new OperationRegistry();
registry.register(makeOperation());
const result = await registry.execute("test.testOp", { value: "hello" }, {} as OperationContext);
expect(result).toEqual({ result: "processed: hello" });
});
it("throws on invalid input", async () => {
const registry = new OperationRegistry();
registry.register(makeOperation());
await expect(
registry.execute("test.testOp", { wrong: "field" }, {} as OperationContext)
).rejects.toThrow();
});
it("throws on missing operation", async () => {
const registry = new OperationRegistry();
await expect(
registry.execute("missing.op", {}, {} as OperationContext)
).rejects.toThrow("Operation not found");
});
it("warns on output mismatch but returns result", async () => {
const registry = new OperationRegistry();
registry.register(makeOperation({
handler: async () => ({ unexpected: "field" }),
}));
const result = await registry.execute("test.testOp", { value: "x" }, {} as OperationContext);
expect(result).toEqual({ unexpected: "field" });
});
});

77
test/validation.test.ts Normal file
View File

@@ -0,0 +1,77 @@
import { describe, it, expect } from "vitest";
import { formatValueErrors, assertIsSchema, validateOrThrow, collectErrors } from "../src/validation.js";
import { Type } from "@alkdev/typebox";
import { KindGuard } from "@alkdev/typebox";
import { Value } from "@alkdev/typebox/value";
describe("formatValueErrors", () => {
it("formats errors with default indent", () => {
const errors = [{ path: "/foo", message: "Expected string" }];
expect(formatValueErrors(errors)).toBe(" - /foo: Expected string");
});
it("formats errors with custom indent", () => {
const errors = [{ path: "/bar", message: "Expected number" }];
expect(formatValueErrors(errors, " * ")).toBe(" * /bar: Expected number");
});
it("formats multiple errors", () => {
const errors = [
{ path: "/a", message: "Error 1" },
{ path: "/b", message: "Error 2" },
];
expect(formatValueErrors(errors)).toBe(" - /a: Error 1\n - /b: Error 2");
});
});
describe("assertIsSchema", () => {
it("passes for valid TypeBox schemas", () => {
expect(() => assertIsSchema(Type.String())).not.toThrow();
});
it("passes for Type.Unknown()", () => {
expect(() => assertIsSchema(Type.Unknown())).not.toThrow();
});
it("throws for plain JSON schema objects", () => {
expect(() => assertIsSchema({ type: "string" })).toThrow("Not a valid TypeBox schema");
});
it("includes context in error message", () => {
expect(() => assertIsSchema({ type: "string" }, "myOp inputSchema")).toThrow(
"for myOp inputSchema"
);
});
});
describe("validateOrThrow", () => {
it("passes for valid input", () => {
const schema = Type.Object({ name: Type.String() });
expect(() => validateOrThrow(schema, { name: "test" })).not.toThrow();
});
it("throws for invalid input", () => {
const schema = Type.Object({ name: Type.String() });
expect(() => validateOrThrow(schema, { name: 123 })).toThrow("Validation failed");
});
it("includes context in error message", () => {
const schema = Type.Object({ name: Type.String() });
expect(() => validateOrThrow(schema, { name: 123 }, "myOp")).toThrow("for myOp");
});
});
describe("collectErrors", () => {
it("returns empty array for valid input", () => {
const schema = Type.Object({ name: Type.String() });
expect(collectErrors(schema, { name: "test" })).toEqual([]);
});
it("returns errors for invalid input", () => {
const schema = Type.Object({ name: Type.String() });
const errors = collectErrors(schema, { name: 123 });
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].path).toBeDefined();
expect(errors[0].message).toBeDefined();
});
});