import { EventEmitter } from 'node:events'; import { IncomingMessage, ServerResponse, Server as Server$1 } from 'node:http'; import { Http2ServerRequest, Http2ServerResponse, SecureServerOptions, Http2Server, Http2SecureServer } from 'node:http2'; import { AddressInfo } from 'node:net'; import { DescService, DescMethod, DescMethodUnary, MessageShape, MessageInitShape, DescMethodServerStreaming, DescMethodClientStreaming, DescMethodBiDiStreaming, DescFile, JsonReadOptions, JsonWriteOptions } from '@bufbuild/protobuf'; import { HandlerContext, Transport, ConnectRouter, ServiceImpl, Interceptor, Client } from '@connectrpc/connect'; /** * Service Catalog — runtime primitives for declarative cross-service calls. * * A catalog is a plain readonly `Record`. It carries * no topology — what is local vs remote lives in `enabledServices` / a * `RemoteResolver` at boot, never in proto. The `@connectum/protoc-gen-catalog` * buf plugin generates a `serviceCatalog` object plus the type augmentations that * make positional `ctx.call(...)` / `ctx.stream(...)` type-safe. * * @module serviceCatalog */ /** * A readonly registry mapping a proto service `typeName` * (e.g. `"orders.v1.OrdersService"`) to its `DescService` descriptor. */ type ServiceCatalog = Readonly>; /** * Module-augmentation target for type-safe **unary** `ctx.call(method, request)`. * * `@connectum/protoc-gen-catalog` augments this with one entry per unary RPC, * keyed `"/"` → `{ request; response }`. It starts empty so * that a project with no generated catalog still type-checks (calls are then * untyped rather than a hard error). */ interface ConnectumCallMap { } /** * Module-augmentation target for type-safe **streaming** `ctx.stream(method, ...)`. * * Augmented per streaming RPC, keyed `"/"` → * `{ request; response; kind }` where `kind` is `"server-stream"`, * `"client-stream"`, or `"bidi"`. Unary RPCs never appear here — they go to * {@link ConnectumCallMap}. */ interface ConnectumStreamMap { } /** * Build a {@link ServiceCatalog} from a literal record, preserving the literal * key type for downstream inference. Equivalent to writing the record inline, * but freezes the result and documents intent. * * Throws {@link CatalogConfigError} if any key does not equal its descriptor's * `typeName` — a mis-keyed entry would bypass the duplicate-`typeName` intent * and break resolution by canonical type name. * * @example * ```ts * const catalog = defineCatalog({ * [OrdersService.typeName]: OrdersService, * [InventoryService.typeName]: InventoryService, * }); * ``` */ declare function defineCatalog>(record: T): Readonly; /** * Merge several catalogs into one. * * Throws {@link CatalogConfigError} on a duplicate `typeName`, or on a key that * does not equal its descriptor's `typeName`. TypeScript cannot catch a duplicate * whose two descriptors have an identical shape (polyrepo finding F3), so this * runtime check is mandatory rather than optional — a silent collision would * route calls to the wrong service. */ declare function mergeCatalogs(...catalogs: readonly ServiceCatalog[]): ServiceCatalog; /** * Connectum handler context. * * Service handlers registered through {@link defineService} receive a * {@link Context} as their second argument. `Context` forwards every field of * the underlying ConnectRPC `HandlerContext` (so `signal`, `timeoutMs()`, * `requestHeader`, `values`, … keep working) and adds the typed `call` * primitive for declarative cross-service calls driven by the service catalog. * * The `call` map is populated by module augmentation of {@link ConnectumCallMap} * (emitted by `@connectum/protoc-gen-catalog`, or hand-written in tests). With * no augmentation `keyof ConnectumCallMap` is `never`, so `ctx.call` is * statically uncallable — exactly the right default for a service that makes no * cross-service calls. * * @module context */ /** * Per-call overrides for {@link Context.call}. * * Every field is optional; omitted dimensions cascade from the incoming * request (see the auto-injection rules on {@link Context.call}). This is the * Connectum catalog `CallOptions`, intentionally distinct from * `@connectrpc/connect`'s client `CallOptions`. */ type CallOptions = { /** * Abort signal for the outgoing call. When omitted, the incoming request's * `ctx.signal` is injected, so cancelling the inbound RPC cancels every * in-flight `ctx.call`. A supplied signal **replaces** the cascade (it is * not linked with `ctx.signal`). */ signal?: AbortSignal; /** * Timeout in milliseconds. When omitted, the remaining inbound deadline * (`ctx.timeoutMs()`) is injected. A caller may **shorten** the deadline, * never extend it (the effective value is `min(timeoutMs, remaining)`). */ timeoutMs?: number; /** * Extra request headers. Only these explicit headers are sent; no inbound * headers are auto-propagated (trace context flows implicitly via the OTel * client interceptor in `outgoingInterceptors`). */ headers?: HeadersInit; /** * Opaque endpoint hint forwarded to the configured `remoteResolver` for * services reachable at several endpoints. Ignored for locally-mounted * services. */ endpoint?: string; }; /** * Push handle for a client-streaming catalog call: send N requests, then * `close()` to receive the single aggregated response. */ interface ClientStreamHandle { /** Enqueue one request message. */ send(request: Req): void; /** End the request stream and resolve with the server's single response. */ close(): Promise; } /** * Push handle for a bidi-streaming catalog call: `send()` requests while * iterating `responses`; `close()` ends only the request (send) half — the * response half keeps yielding until the server completes. */ interface BidiStreamHandle { /** Enqueue one request message. */ send(request: Req): void; /** End the request (send) half; the response half is unaffected. */ close(): void; /** The server's response messages, in order. */ readonly responses: AsyncIterable; } /** * Maps a {@link ConnectumStreamMap} entry to the ergonomic shape returned by * {@link Context.stream}, discriminated by the entry's `kind`. */ type StreamReturn = E extends { kind: "server-stream"; request: infer Req; response: infer Res; } ? (request: Req, options?: CallOptions) => AsyncIterable : E extends { kind: "client-stream"; request: infer Req; response: infer Res; } ? (options?: CallOptions) => ClientStreamHandle : E extends { kind: "bidi"; request: infer Req; response: infer Res; } ? (options?: CallOptions) => BidiStreamHandle : never; /** * The typed **unary** catalog-call surface: `call(method, request, options?)` * keyed off {@link ConnectumCallMap}. Shared by the handler {@link Context} and * the standalone `CatalogClient` (`createCatalogClient`) so both expose an * identical, fully-typed `call`. * * @typeParam K - A `"${typeName}/${Method}"` key of {@link ConnectumCallMap}. */ type CatalogCall = (method: K, request: ConnectumCallMap[K]["request"], options?: CallOptions) => Promise; /** * The typed **streaming** catalog-call surface: `stream(method)` returns a * kind-specific factory keyed off {@link ConnectumStreamMap}. Shared by the * handler {@link Context} and the standalone `CatalogClient`. * * @typeParam K - A `"${typeName}/${Method}"` key of {@link ConnectumStreamMap}. */ type CatalogStream = (method: K) => StreamReturn; /** * The context object passed to every Connectum service handler. * * Extends ConnectRPC's `HandlerContext` (all of its fields remain available) * and adds {@link Context.call} (unary catalog calls) and {@link Context.stream} * (streaming catalog calls). */ interface Context extends HandlerContext { /** * Invoke a unary service in the catalog. The transport is chosen * automatically: an in-process call when the target is mounted locally, * otherwise the `remoteResolver`-supplied transport. * * `signal` and `timeoutMs` cascade from the incoming request unless * overridden in `options` (see {@link CallOptions}). * * @typeParam K - A `"${typeName}/${Method}"` key of {@link ConnectumCallMap}. */ call: CatalogCall; /** * Open a streaming call to a service in the catalog. Returns a kind-specific * factory: server-streaming yields an `AsyncIterable`; client- and * bidi-streaming return push handles (see {@link ClientStreamHandle} / * {@link BidiStreamHandle}). * * On a mid-stream transport failure the iterator delivers the messages * received so far and then throws the terminal `ConnectError`. * * @typeParam K - A `"${typeName}/${Method}"` key of {@link ConnectumStreamMap}. */ stream: CatalogStream; } /** * The implementation of a single RPC, receiving a Connectum {@link Context}. * * Mirrors `@connectrpc/connect`'s `MethodImpl` but substitutes `Context` for * the raw `HandlerContext`, so `ctx.call` is visible inside handlers. */ type ConnectumMethodImpl = M extends DescMethodUnary ? (request: MessageShape, context: Context) => Promise> | MessageInitShape : M extends DescMethodServerStreaming ? (request: MessageShape, context: Context) => AsyncIterable> : M extends DescMethodClientStreaming ? (requests: AsyncIterable>, context: Context) => Promise> : M extends DescMethodBiDiStreaming ? (requests: AsyncIterable>, context: Context) => AsyncIterable> : never; /** * The full implementation of a service: one {@link ConnectumMethodImpl} per * method. Accepted by {@link defineService} / {@link defineLazyService}. * * Mirrors `@connectrpc/connect`'s `ServiceImpl` with the Connectum * {@link Context}. */ type ConnectumServiceImpl = { [P in keyof Desc["method"]]: ConnectumMethodImpl; }; /** * RemoteResolver — resolves a remote (non-locally-mounted) service to a * ConnectRPC `Transport`. * * The framework caches the result per unique `(typeName, endpoint)` key, so a * resolver MUST be **synchronous** and MUST NOT perform network I/O (TCP dial, * DNS lookup) — it only maps a service identity to a lazily-connecting * `Transport`. Returning `null` means "no route" → the call fails with * `Code.Unavailable`. * * @module remoteResolver */ /** Context handed to a {@link RemoteResolver} for a single resolution. */ interface ResolverContext { /** Proto service `typeName`, e.g. `"orders.v1.OrdersService"`. */ readonly typeName: string; /** Opaque endpoint hint from `CallOptions.endpoint` (polymorphic deployments). */ readonly endpoint?: string; } /** * Resolve a remote service to a `Transport`, or `null` if there is no route. * Synchronous by contract — see the module note. */ type RemoteResolver = (ctx: ResolverContext) => Transport | null; /** * A resolver that routes every remote service to the same `Transport`. Useful * for a single upstream (sidecar, gateway) that fronts all remote services. */ declare function singleTransportResolver(transport: Transport): RemoteResolver; /** * A resolver backed by an explicit `{ [typeName]: Transport }` map. Unknown * typeNames resolve to `null` (→ `Code.Unavailable`). */ declare function mapResolver(map: Readonly>): RemoteResolver; /** Options for {@link dnsResolver}. */ interface DnsResolverOptions { /** * URL template with `{shortName}` (alias `{name}`) placeholders. The short * name is the last `typeName` segment, lower-cased, minus a trailing * `Service` (e.g. `orders.v1.OrdersService` → `orders`). A k8s/DNS route is * expressed directly, e.g. `"http://{shortName}.prod.svc.cluster.local:50051"`. */ readonly template: string; /** Build a `Transport` from the resolved base URL. Defaults to a gRPC (HTTP/2) transport. */ readonly createTransport?: (baseUrl: string) => Transport; } /** * A resolver that derives a base URL per service from a DNS-style template and * builds a transport for it. Mirrors typical container/k8s service-name routing. * Always resolves (never `null`) — the template is assumed to cover every remote * service; use {@link mapResolver} for an explicit allow-list. */ declare function dnsResolver(options: DnsResolverOptions): RemoteResolver; /** Options for {@link perServiceEnvResolver}. */ interface PerServiceEnvResolverOptions { /** Build a `Transport` from the resolved base URL. Defaults to a gRPC (HTTP/2) transport. */ readonly createTransport?: (baseUrl: string) => Transport; } /** * A resolver backed by per-service environment variables: `map` pairs each * `typeName` with the name of the env var holding its base URL. A service with * no mapping, or whose env var is unset/empty, resolves to `null` * (→ `Code.Unavailable`). Replaces hand-rolled env registries in boot code. * * @example `perServiceEnvResolver({ "orders.v1.OrdersService": "ORDERS_URL" })` */ declare function perServiceEnvResolver(map: Readonly>, options?: PerServiceEnvResolverOptions): RemoteResolver; /** * defineService — the canonical way to register a service on a Connectum server. * * A {@link ServiceDefinition} pairs a proto `DescService` descriptor with a * closure that mounts its handlers on a `ConnectRouter`. Keeping the descriptor * alongside the registration closure lets the framework build the service * catalog, drive `enabledServices` activation, and validate the transport * without re-deriving identity from the router. * * Handlers receive a Connectum {@link Context} (the raw ConnectRPC * `HandlerContext` plus the typed `ctx.call`). The framework supplies a * {@link RegisterContext} at mount time so the registration closure can wrap * the user handlers without `defineService` needing a server reference. * * @module defineService */ /** * Per-service handler options forwarded to ConnectRPC's `router.service()` — * e.g. per-service `interceptors` (applied to every method of this service) and * `jsonOptions`. Derived from the underlying `ConnectRouter.service` signature * so it always matches the installed `@connectrpc/connect`. */ type ServiceOptions = NonNullable[2]>; /** * Framework-supplied helpers handed to a {@link ServiceDefinition}'s `register` * closure at mount time. Currently exposes the handler wrapper that injects the * Connectum {@link Context}. * * @internal */ interface RegisterContext { /** * Wrap a user service implementation so each method receives a Connectum * `Context` in place of the raw ConnectRPC `HandlerContext`. */ readonly wrapHandlers: (descriptor: S, handlers: ConnectumServiceImpl) => ServiceImpl; } /** * A service ready to be mounted: its proto descriptor plus a `register` closure * that wires the handlers onto a `ConnectRouter`. Produced by {@link defineService} * and {@link defineLazyService}; consumed by `createServer({ services })`. */ interface ServiceDefinition { /** The proto service descriptor (carries `typeName` and `file`). */ readonly descriptor: DescService; /** Mounts the service's handlers on the given router. @internal */ readonly register: (router: ConnectRouter, ctx: RegisterContext) => void; } /** * Define a service from its descriptor and handler map. * * Pass {@link ServiceOptions} to set per-service handler options, e.g. * interceptors applied to every method of this service: * * @example * ```ts * const greeter = defineService(GreeterService, { * async sayHello(req, ctx) { * // ctx.call(...) is available for cross-service calls * return { message: `Hello, ${req.name}!` }; * }, * }, { interceptors: [requireAuth, auditLog] }); * createServer({ services: [greeter] }); * ``` */ declare function defineService(descriptor: S, handlers: ConnectumServiceImpl, options?: ServiceOptions): ServiceDefinition; /** * Define a service whose handlers (and their dependencies) are created lazily. * * `factory` runs only when the service is actually mounted locally — i.e. when * it is in `enabledServices` (or `enabledServices` is `undefined`). A service * routed to a remote process never instantiates its local dependencies. Useful * for DI-heavy monoliths where wiring a service is expensive. */ declare function defineLazyService(descriptor: S, factory: () => ConnectumServiceImpl, options?: ServiceOptions): ServiceDefinition; /** * Public API types for Server * * @module types */ /** Incoming request — HTTP/1.1 or HTTP/2 */ type NodeRequest = IncomingMessage | Http2ServerRequest; /** Server response — HTTP/1.1 or HTTP/2 */ type NodeResponse = ServerResponse | Http2ServerResponse; /** Underlying transport server — HTTP/1.1, HTTP/2 plaintext, or HTTP/2 TLS */ type TransportServer = Server$1 | Http2Server | Http2SecureServer; /** * Shutdown hook function type * * A function called during graceful shutdown. May be synchronous or async. */ type ShutdownHook = () => void | Promise; /** * Context provided to protocol registration functions * * Contains information about registered services that protocols * may need (e.g., reflection needs DescFile[], healthcheck needs service names). */ interface ProtocolContext { /** Registered service file descriptors */ readonly registry: ReadonlyArray; } /** * HTTP handler for protocol-specific endpoints * * @returns true if the request was handled, false otherwise */ type HttpHandler = (req: NodeRequest, res: NodeResponse) => boolean; /** * Protocol registration interface * * Protocols (healthcheck, reflection, custom) implement this interface * to register themselves on the server's ConnectRouter. * * @example * ```typescript * const myProtocol: ProtocolRegistration = { * name: "my-protocol", * register(router, context) { * router.service(MyService, myImpl); * }, * }; * * const server = createServer({ * services: [routes], * protocols: [myProtocol], * }); * ``` */ interface ProtocolRegistration { /** Protocol name for identification (e.g., "healthcheck", "reflection") */ readonly name: string; /** Register protocol services on the router */ register(router: ConnectRouter, context: ProtocolContext): void; /** Optional HTTP handler for fallback routing (e.g., /healthz endpoint) */ httpHandler?: HttpHandler; } /** * TLS configuration options */ interface TLSOptions { /** * Path to TLS key file */ keyPath?: string; /** * Path to TLS certificate file */ certPath?: string; /** * TLS directory path (alternative to keyPath/certPath) * Will look for server.key and server.crt in this directory */ dirPath?: string; } /** * Minimal interface for event bus lifecycle integration with the server. * * Packages implementing event bus adapters (e.g., @connectum/events) * must satisfy this interface to be used with `createServer({ eventBus })`. */ interface EventBusLike { /** * Start the event bus (connect to broker, set up subscriptions). * * @param options - Optional start parameters * @param options.signal - Abort signal from server for graceful shutdown */ start(options?: { signal?: AbortSignal; }): Promise; /** Stop the event bus (drain subscriptions, disconnect) */ stop(): Promise; } /** * Server state constants * * Note: Using const object instead of enum for native TypeScript compatibility */ declare const ServerState: { /** Server created but not started */ readonly CREATED: "created"; /** Server is starting */ readonly STARTING: "starting"; /** Server is running and accepting connections */ readonly RUNNING: "running"; /** Server is stopping */ readonly STOPPING: "stopping"; /** Server has stopped */ readonly STOPPED: "stopped"; }; type ServerState = (typeof ServerState)[keyof typeof ServerState]; /** * Lifecycle event names */ declare const LifecycleEvent: { /** Emitted when server starts (before ready) */ readonly START: "start"; /** Emitted when server is ready to accept connections */ readonly READY: "ready"; /** Emitted when server begins graceful shutdown */ readonly STOPPING: "stopping"; /** Emitted when server stops */ readonly STOP: "stop"; /** Emitted on error */ readonly ERROR: "error"; }; type LifecycleEvent = (typeof LifecycleEvent)[keyof typeof LifecycleEvent]; /** * Graceful shutdown options */ interface ShutdownOptions { /** * Timeout in milliseconds for graceful shutdown * @default 30000 */ timeout?: number; /** * Signals to listen for graceful shutdown * @default ["SIGTERM", "SIGINT"] */ signals?: NodeJS.Signals[]; /** * Enable automatic graceful shutdown on signals * @default false */ autoShutdown?: boolean; /** * Force close all HTTP/2 sessions when shutdown timeout is exceeded. * When true, sessions are destroyed after timeout. When false, server * waits indefinitely for in-flight requests to complete. * @default true */ forceCloseOnTimeout?: boolean; } /** * Server configuration options for createServer() */ interface CreateServerOptions { /** * Service routes to register */ services: readonly ServiceDefinition[]; /** * Server port * @default 5000 */ port?: number; /** * Server host to bind * @default "0.0.0.0" */ host?: string; /** * TLS configuration */ tls?: TLSOptions; /** * Protocol registrations (healthcheck, reflection, custom) * * @example * ```typescript * import { Healthcheck } from '@connectum/healthcheck'; * import { Reflection } from '@connectum/reflection'; * * const server = createServer({ * services: [routes], * protocols: [Healthcheck({ httpEnabled: true }), Reflection()], * }); * ``` */ protocols?: ProtocolRegistration[]; /** * Graceful shutdown configuration */ shutdown?: ShutdownOptions; /** * ConnectRPC interceptors. * When omitted or `[]`, no interceptors are applied. * Use `createDefaultInterceptors()` from `@connectum/interceptors` to get the default chain. */ interceptors?: Interceptor[]; /** * Event bus instance for pub/sub messaging. * * The event bus is started during `server.start()` (after route building, * before transport listen) and stopped during graceful shutdown. * * @example * ```typescript * import { createEventBus } from '@connectum/events'; * import { NatsAdapter } from '@connectum/events-nats'; * * const eventBus = createEventBus({ * adapter: NatsAdapter({ servers: ['nats://localhost:4222'] }), * router: eventRouter, * }); * * const server = createServer({ * services: [routes], * eventBus, * }); * ``` */ eventBus?: EventBusLike; /** * Allow HTTP/1.1 connections. * * With TLS: enables ALPN negotiation (both HTTP/1.1 and HTTP/2). * Without TLS: creates HTTP/1.1 server (http.createServer). * Set to false without TLS for h2c-only (http2.createServer). * * @default true */ allowHTTP1?: boolean; /** * Startup validation of streaming method kinds vs the effective transport. * * Bidi-streaming methods require HTTP/2 (Connect protocol: "Bidirectional * streaming requires HTTP/2, but the other RPC types also support * HTTP/1.1"). On a plaintext HTTP/1.1 server (no TLS + `allowHTTP1: true`, * the default) they fail silently at runtime — the first send hangs * forever. With `"error"` (default) `start()` rejects with a * `TransportValidationError` (code `CONNECTUM_UNSUPPORTED_STREAMING_TRANSPORT`) * naming the affected methods and both fixes; `"warn"` logs once and * starts anyway; `"off"` skips the check. * * On a TLS server that also allows HTTP/1.1 (`allowHTTP1: true`), bidi * works for HTTP/2 clients but a client negotiating HTTP/1.1 over TLS * hits the same hang — this residual risk is always a one-time warning * (never a hard error), silenced only by `"off"`. Set `allowHTTP1: false` * to remove the risk (the server refuses HTTP/1.1 at ALPN). * * @default "error" */ transportValidation?: "error" | "warn" | "off"; /** * Handshake timeout in milliseconds * @default 30000 */ handshakeTimeout?: number; /** * Additional HTTP/2 server options */ http2Options?: SecureServerOptions; /** * Connect JSON serialization options applied server-wide. * * Passed through to the underlying `connectNodeAdapter`, so it affects every * registered service and protocol (e.g. healthcheck, reflection). The most * common use is `alwaysEmitImplicit: true`, which includes fields with * implicit presence (proto3 scalar `0`, empty string/list, enum default) in * JSON responses instead of omitting them. * * For per-service control, pass the same option as the third argument of * `router.service()` inside a {@link ServiceDefinition}'s `register` closure * instead. * * Note: the relevant `JsonWriteOptions` field in `@bufbuild/protobuf` v2 is * `alwaysEmitImplicit` (named `emitDefaultValues` in v1). * * @example * ```typescript * const server = createServer({ * services: [routes], * jsonOptions: { alwaysEmitImplicit: true }, * }); * ``` */ jsonOptions?: Partial; /** * The full set of services known to the system, `typeName → DescService` * (typically the generated `serviceCatalog`). Drives startup validation and * remote routing. Optional — a process that hosts everything locally and * makes no cross-service calls needs no catalog. */ catalog?: ServiceCatalog; /** * Proto `typeName`s to mount **locally** from `services`. A service in * `services` whose `typeName` is not listed is treated as remote (resolved * via {@link CreateServerOptions.remoteResolver}). `undefined` mounts every * provided service locally. */ enabledServices?: readonly string[]; /** * Resolves a service that is not mounted locally to a `Transport`. Consulted * by `server.client()` (and `ctx.call`) for remote services. Synchronous and * must not perform network I/O — see {@link RemoteResolver}. */ remoteResolver?: RemoteResolver; /** * Client-side interceptors applied to every outgoing `server.client()` / * `ctx.call` call (cross-cutting concerns like auth or logging), so call * sites stay free of boilerplate. */ outgoingInterceptors?: readonly Interceptor[]; /** * Inbound header names to copy onto every outgoing `ctx.call` / `ctx.stream`. * Empty by default — no header is propagated implicitly. Explicit * `CallOptions.headers` always win over a propagated value. * * Use {@link defaultPropagateHeaders} (W3C trace-context headers) as a base * and add your own, e.g. `[...defaultPropagateHeaders, "x-tenant-id"]`. */ propagateHeaders?: readonly string[]; } /** * Server interface with explicit lifecycle control * * @example * ```typescript * import { createServer } from '@connectum/core'; * * const server = createServer({ * services: [myRoutes], * port: 5000 * }); * * server.on('ready', () => console.log('Server ready!')); * server.on('error', (err) => console.error('Error:', err)); * * await server.start(); * * // Later * await server.stop(); * ``` */ interface Server extends EventEmitter { /** * Start the server * * @throws Error if server is not in CREATED state */ start(): Promise; /** * Stop the server gracefully * * @throws Error if server is not in RUNNING state */ stop(): Promise; /** * Current server address * * Returns null until server is started */ readonly address: AddressInfo | null; /** * Whether server is currently running */ readonly isRunning: boolean; /** * Current server state */ readonly state: ServerState; /** * Register listener for lifecycle events */ on(event: "start", listener: () => void): this; on(event: "ready", listener: () => void): this; on(event: "stopping", listener: () => void): this; on(event: "stop", listener: () => void): this; on(event: "error", listener: (error: Error) => void): this; /** * Register one-time listener for lifecycle events */ once(event: "start", listener: () => void): this; once(event: "ready", listener: () => void): this; once(event: "stopping", listener: () => void): this; once(event: "stop", listener: () => void): this; once(event: "error", listener: (error: Error) => void): this; /** * Remove listener for lifecycle events */ off(event: "start", listener: () => void): this; off(event: "ready", listener: () => void): this; off(event: "stopping", listener: () => void): this; off(event: "stop", listener: () => void): this; off(event: "error", listener: (error: Error) => void): this; /** * Add a service route at runtime * * @throws Error if server is already running */ addService(service: ServiceDefinition): void; /** * Add an interceptor at runtime * * @throws Error if server is already running */ addInterceptor(interceptor: Interceptor): void; /** * Add a protocol at runtime * * @throws Error if server is already running */ addProtocol(protocol: ProtocolRegistration): void; /** * Register an anonymous shutdown hook * * @param handler - Shutdown hook function * @throws Error if server is already stopped */ onShutdown(handler: ShutdownHook): void; /** * Register a named shutdown hook * * @param name - Module name for dependency resolution * @param handler - Shutdown hook function * @throws Error if server is already stopped */ onShutdown(name: string, handler: ShutdownHook): void; /** * Register a named shutdown hook with dependencies * * Dependencies are executed before this hook during shutdown. * * @param name - Module name for dependency resolution * @param dependencies - Module names that must shut down first * @param handler - Shutdown hook function * @throws Error if server is already stopped */ onShutdown(name: string, dependencies: string[], handler: ShutdownHook): void; /** * Abort signal that is aborted when server begins shutdown. * * Use this to signal streaming RPCs and long-running operations * that the server is shutting down. */ readonly shutdownSignal: AbortSignal; /** * Underlying transport server * * Returns null until server is started */ readonly transport: TransportServer | null; /** * Registered service routes */ readonly routes: ReadonlyArray; /** * Registered interceptors */ readonly interceptors: ReadonlyArray; /** * Registered protocols */ readonly protocols: ReadonlyArray; /** * Event bus instance, if configured * * Returns null if no event bus was provided to createServer(). */ readonly eventBus: EventBusLike | null; /** * Create a fully-typed ConnectRPC client that dispatches calls directly * to handlers registered on this server, without opening any TCP socket. * * Safe to call before `server.start()` — the routes are materialized * lazily on first access. Once materialized, `addService` / `addInterceptor` * / `addProtocol` will throw. * * @example * ```typescript * import { GreeterService } from './gen/greeter_pb.js'; * * const server = createServer({ services: [routes] }); * const client = server.localClient(GreeterService); * const response = await client.sayHello({ name: 'world' }); * ``` */ localClient(service: T): Client; /** * Synchronous registry lookup: returns whether the given proto service * descriptor is served locally by this `Server`. Triggers route * materialization on first call. * * Source of truth is the same `ConnectRouter.service(desc, impl)` chain * used to build the HTTP handler — no separate registration step. * * @example * ```typescript * if (server.hasService(GreeterService)) { * // routed in-process * } * ``` */ hasService(desc: DescService): boolean; /** * Unified client factory: auto-routes to the in-process transport if the * service is registered on this `Server`, otherwise to the transport * supplied by the configured `remoteResolver` (e.g. a * `createGrpcTransport({ baseUrl })` to a remote peer). An optional * `options.endpoint` hint is forwarded to the resolver. * * Fail-fast (split error model): a non-local service with no `remoteResolver` * configured is a configuration mistake → throws {@link CatalogConfigError} * at the `server.client(...)` call. A resolver that returns `null` is an * operational miss → `ConnectError(Code.Unavailable)`. * * Enables polyglot deployments where the same call site (`server.client(S)`) * routes locally in a modular monolith and remotely when the service is * split into a separate process — without code changes. * * @example * ```typescript * // Configure the resolver once; the same call works whether GreeterService * // is co-located or remote: * const server = createServer({ services: [...], remoteResolver }); * const client = server.client(GreeterService); * await client.sayHello({ name: 'world' }); * ``` */ client(service: T, options?: ServerClientOptions): Client; } /** * Options for {@link Server.client}. */ interface ServerClientOptions { /** * Opaque endpoint hint forwarded to the configured `remoteResolver` when the * requested service is not mounted locally (polymorphic deployments — one * proto served at several endpoints). Ignored for locally-mounted services. */ endpoint?: string; } export { mapResolver as A, type BidiStreamHandle as B, type CatalogCall as C, type DnsResolverOptions as D, type EventBusLike as E, mergeCatalogs as F, perServiceEnvResolver as G, type HttpHandler as H, singleTransportResolver as I, LifecycleEvent as L, type NodeRequest as N, type PerServiceEnvResolverOptions as P, type RemoteResolver as R, type ServiceCatalog as S, type TLSOptions as T, type CatalogStream as a, type Server as b, type CreateServerOptions as c, type CallOptions as d, type ClientStreamHandle as e, type ConnectumCallMap as f, type ConnectumMethodImpl as g, type ConnectumServiceImpl as h, type ConnectumStreamMap as i, type Context as j, type NodeResponse as k, type ProtocolContext as l, type ProtocolRegistration as m, type ResolverContext as n, type ServerClientOptions as o, ServerState as p, type ServiceDefinition as q, type ServiceOptions as r, type ShutdownHook as s, type ShutdownOptions as t, type StreamReturn as u, type TransportServer as v, defineCatalog as w, defineLazyService as x, defineService as y, dnsResolver as z };