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
11 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-04-30 |
API Surface
All public types, registry, call protocol, subscribe, env, validation, and adapters. See call-protocol.md for detailed call protocol semantics and adapters.md for adapter internals.
Core Types
OperationType
enum OperationType {
QUERY = "query",
MUTATION = "mutation",
SUBSCRIPTION = "subscription",
}
QUERY— read-only, no side effectsMUTATION— write, has side effectsSUBSCRIPTION— async generator, yields multiple values over time
Identity
interface Identity {
id: string
scopes: string[]
resources?: Record<string, string[]>
}
Caller security context. scopes are global permissions (AND-checked against requiredScopes). resources maps "type:id" to action arrays (checked against resourceType/resourceAction). Derived from keypal ApiKeyMetadata.
AccessControl
type AccessControl = Static<typeof AccessControlSchema>
const AccessControlSchema = Type.Object({
requiredScopes: Type.Array(Type.String()),
requiredScopesAny: Type.Optional(Type.Array(Type.String())),
resourceType: Type.Optional(Type.String()),
resourceAction: Type.Optional(Type.String()),
customAuth: Type.Optional(Type.String()),
})
| Field | Semantics |
|---|---|
requiredScopes |
AND — caller must have ALL listed scopes |
requiredScopesAny |
OR — caller must have at least ONE listed scope |
resourceType |
Resource category for resource-scoped checks |
resourceAction |
Required action on the resource |
customAuth |
Name of custom auth function (not yet enforced) |
ErrorDefinition
type ErrorDefinition = Static<typeof ErrorDefinitionSchema>
const ErrorDefinitionSchema = Type.Object({
code: Type.String(),
description: Type.String(),
schema: Type.Unknown(),
httpStatus: Type.Optional(Type.Number()),
})
Declared on IOperationDefinition.errorSchemas. Contract between operation and callers about what errors it may produce.
OperationContext
type OperationContext = Static<typeof OperationContextSchema> & {
env?: OperationEnv
stream?: () => AsyncIterable<unknown>
pubsub?: unknown
}
Passed to every handler. env provides namespace-keyed access to other operations (via buildEnv). stream and pubsub support subscription and event patterns.
OperationSpec
interface OperationSpec<TInput = unknown, TOutput = unknown> {
name: string
namespace: string
version: string
type: OperationType
title?: string
description: string
tags?: string[]
inputSchema: TSchema
outputSchema: TSchema
errorSchemas?: ErrorDefinition[]
accessControl: AccessControl
_meta?: Record<string, unknown>
}
Serializable, hashable subset of an operation definition. No handler — safe to send over the wire.
IOperationDefinition
interface IOperationDefinition<TInput, TOutput, TContext> extends OperationSpec<TInput, TOutput> {
handler: OperationHandler<TInput, TOutput, TContext> | SubscriptionHandler<TInput, TOutput, TContext>
}
Full definition including the runtime handler. Registered with OperationRegistry.
OperationHandler / SubscriptionHandler
type OperationHandler<TInput, TOutput, TContext> = (
input: TInput, context: TContext,
) => Promise<TOutput> | TOutput
type SubscriptionHandler<TInput, TOutput, TContext> = (
input: TInput, context: TContext,
) => AsyncGenerator<TOutput, void, unknown>
OperationHandler returns a single value. SubscriptionHandler yields values over time.
OperationEnv
type OperationEnv = Record<string, Record<string, (input: unknown) => Promise<unknown>>>
Namespace-keyed operation map. Accessed as env.namespace.operationName(input). Created by buildEnv.
Registry
OperationRegistry
| Method | Signature | Description |
|---|---|---|
register(operation) |
(operation: IOperationDefinition) => void |
Register by {namespace}.{name} key. Validates schemas. |
registerAll(operations) |
(operations: IOperationDefinition[]) => void |
Bulk register. |
get(id) |
(id: string) => IOperationDefinition | undefined |
Get by full id ("namespace.name"). |
getByName(namespace, name) |
(namespace: string, name: string) => IOperationDefinition | undefined |
Get by parts. |
list() |
() => IOperationDefinition[] |
All registered operations. |
getSpec(id) |
(id: string) => OperationSpec | undefined |
Serializable spec (no handler). |
getAllSpecs() |
() => OperationSpec[] |
All serializable specs. |
execute(operationId, input, context) |
(id: string, input: TInput, ctx: OperationContext) => Promise<TOutput> |
Validate input, run handler, warn on output mismatch. Throws if not found or validation fails. |
Registration key format: {namespace}.{name}. Overwrite on duplicate.
execute validates input with validateOrThrow before calling the handler. Output validation uses collectErrors and logs warnings — it does not throw.
Call Protocol
PendingRequestMap
See call-protocol.md for full semantics.
| Method | Signature | Description |
|---|---|---|
constructor(eventTarget?) |
(eventTarget?: EventTarget) |
Creates internal pubsub, wires subscription handlers for responded/error/aborted. |
call(operationId, input, options?) |
Promise<unknown> |
Publish call.requested, return Promise that resolves on call.responded. |
respond(requestId, output) |
void |
Publish call.responded. |
emitError(requestId, code, message, details?) |
void |
Publish call.error. |
abort(requestId) |
void |
Publish call.aborted, reject pending Promise. |
getPendingCount() |
number |
Number of in-flight requests. |
CallHandler
type CallHandler = (event: CallRequestedEvent) => Promise<void>
Created by buildCallHandler({ registry, eventTarget? }). Subscribes to call.requested, checks access control, validates input, executes via registry. On success: no-op (handler is expected to publish call.responded through the PendingRequestMap). On failure: throws CallError.
CallEventMap
const CallEventMap = {
"call.requested": Type.Object({ ... }),
"call.responded": Type.Object({ ... }),
"call.aborted": Type.Object({ ... }),
"call.error": Type.Object({ ... }),
}
Typed event map compatible with @alkdev/pubsub. See call-protocol.md for event shapes.
Event Types
| Type | Fields | Description |
|---|---|---|
CallRequestedEvent |
requestId, operationId, input, parentRequestId?, deadline?, identity? |
Initiates a call |
CallRespondedEvent |
requestId, output |
Successful response |
CallAbortedEvent |
requestId |
Call cancelled |
CallErrorEvent |
requestId, code, message, details? |
Error response |
Subscribe
subscribe
function subscribe(
registry: OperationRegistry,
operationId: string,
input: unknown,
context: OperationContext,
): AsyncGenerator<unknown, void, unknown>
Direct subscription execution. Gets the operation, casts its handler to AsyncGenerator, yields each value. Properly cleans up the generator on iteration stop (calls generator.return() in finally).
This is the synchronous alternative to the call protocol's call.requested → call.responded flow for subscriptions. Use subscribe() for in-process subscription consumption; use PendingRequestMap for cross-transport subscription.
Env Builder
buildEnv
function buildEnv(options: EnvOptions): OperationEnv
interface EnvOptions {
registry: OperationRegistry
context: OperationContext
allowedNamespaces?: string[]
callMap?: PendingRequestMap
}
Creates a namespace-keyed OperationEnv for nested operation calls. Two modes:
- Direct mode:
buildEnv({ registry, context })— env functions callregistry.execute() - Call protocol mode:
buildEnv({ registry, context, callMap })— env functions callcallMap.call(), publishingcall.requestedevents withparentRequestIdfor call graph tracking
SUBSCRIPTION operations are filtered out — env only provides QUERY and MUTATION operations for nested calls.
allowedNamespaces restricts which namespaces are available.
Validation
| Export | Signature | Description |
|---|---|---|
assertIsSchema(schema, context?) |
(unknown, string?) => void |
Throws if schema is not a valid TypeBox schema. |
validateOrThrow(schema, value, context?) |
(TSchema, unknown, string?) => void |
Throws with formatted errors if value fails schema check. |
collectErrors(schema, value) |
(TSchema, unknown) => Array<{path, message}> |
Returns errors array (empty if valid). |
formatValueErrors(errors, indent?) |
(Iterable<{path, message}>, string?) => string |
Human-readable error formatting. |
Error Model
CallError
class CallError extends Error {
readonly code: CallErrorCode
readonly details?: unknown
constructor(code: CallErrorCode, message: string, details?: unknown)
}
InfrastructureErrorCode
enum InfrastructureErrorCode {
OPERATION_NOT_FOUND = "OPERATION_NOT_FOUND",
ACCESS_DENIED = "ACCESS_DENIED",
VALIDATION_ERROR = "VALIDATION_ERROR",
TIMEOUT = "TIMEOUT",
ABORTED = "ABORTED",
EXECUTION_ERROR = "EXECUTION_ERROR",
UNKNOWN_ERROR = "UNKNOWN_ERROR",
}
CallErrorCode is InfrastructureErrorCode | string — domain codes from errorSchemas are plain strings.
mapError
function mapError(error: unknown, errorSchemas?: { code: string; schema: unknown }[]): CallError
Converts any thrown value to CallError. If the thrown value is already a CallError, returns it. If it's an Error and errorSchemas are provided, matches against declared error codes. Falls back to EXECUTION_ERROR for unmatched Error instances and UNKNOWN_ERROR for non-Error values.
Schema Conversion
FromSchema
function FromSchema<T>(T: T): TSchema
Converts JSON Schema to TypeBox TSchema. Handles: allOf, anyOf, oneOf, enum, object (with required tracking), tuple, array, const, $ref, primitives (string, number, integer, boolean, null). Unknown shapes fall back to Type.Unknown().
Used internally by FromOpenAPI to convert OpenAPI JSON Schema definitions to TypeBox. Also used by from_mcp to convert MCP tool inputSchema (which is JSON Schema).
Adapters
See adapters.md for detailed adapter documentation.
| Adapter | Import | Description |
|---|---|---|
FromOpenAPI |
Main barrel | OpenAPI spec → IOperationDefinition[] |
FromOpenAPIFile |
Main barrel | OpenAPI file → IOperationDefinition[] |
FromOpenAPIUrl |
Main barrel | OpenAPI URL → IOperationDefinition[] |
createMCPClient |
from-mcp sub-path |
MCP server → MCPClientWrapper with tool operations |
closeMCPClient |
from-mcp sub-path |
Close MCP client connection |
MCPClientLoader |
from-mcp sub-path |
Manage multiple MCP servers |
scanOperations |
Main barrel | Filesystem auto-discovery of operation definitions |