Service Catalog
How does Orders know that Inventory has a CheckStock method, accepts a SKU and returns a stock count? From protobuf contracts, not by scanning the network. The service catalog brings those known contracts into the application.
Both proto files are inputs to the same generation run. One generated catalog already contains both services.
app/proto/shop/v1/orders.protoOrdersServiceproto/shop/v1/inventory.protoInventoryServicebuf generateprotobuf-es + catalog pluginDescriptors from
*_pb.tsimport { serviceCatalog }catalog: ServiceCatalogshop.v1.OrdersService
→ OrdersServiceshop.v1.InventoryService
→ InventoryServicecreateServer( { catalog, ... }).ConnectumCallMapLoaded by the generated importsmergeCatalogs merges values, not types.Application imports for this layout
import { serviceCatalog } from './gen/catalog.gen.js';
export const catalog = serviceCatalog;
Look inside a catalog entry
The key is the full proto typeName. Its value is a DescService descriptor, not a handler or deployment address.
- Descriptor
OrdersService- Unary method
CheckAvailability- Request
CheckAvailabilityRequestsku: string- Response
CheckAvailabilityResponsesku: string; available: boolean
- Descriptor
InventoryService- Unary method
CheckStock- Request
CheckStockRequestsku: string- Response
CheckStockResponseunits: number
These examples use unary RPCs. Streaming methods contribute to ConnectumStreamMap instead.
Read all three build layouts
One folder. Both proto files are inputs to the same generation run. One generated catalog already contains both services.
app/→buf generate→gen/catalog.gen.ts
import { serviceCatalog } from './gen/catalog.gen.js';
export const catalog = serviceCatalog;
Several source directories can also feed one generation run. Moving a file into another folder does not require merging catalogs.
Separate packages. Each directory is an independently generated contract package. The application imports the two parts and merges their runtime maps.
packages/orders-contracts/→buf generate→gen/catalog.gen.tspackages/inventory-contracts/→buf generate→gen/catalog.gen.ts
import { mergeCatalogs } from '@connectum/core';
import { serviceCatalog as ordersCatalog } from '@shop/orders-contracts';
import { serviceCatalog as inventoryCatalog } from '@shop/inventory-contracts';
export const catalog = mergeCatalogs(ordersCatalog, inventoryCatalog);
The split here is a generation/package boundary, not just two folders. The package exports must also expose the generated type augmentations.
Separate repositories. Each repository builds a contract package. The consumer installs versioned dependencies, then uses the same imports and merge as in a monorepo.
orders-contracts repo/→buf generate→gen/catalog.gen.ts→@shop/[email protected]inventory-contracts repo/→buf generate→gen/catalog.gen.ts→@shop/[email protected]
import { mergeCatalogs } from '@connectum/core';
import { serviceCatalog as ordersCatalog } from '@shop/orders-contracts';
import { serviceCatalog as inventoryCatalog } from '@shop/inventory-contracts';
export const catalog = mergeCatalogs(ordersCatalog, inventoryCatalog);
Package names and 1.0.0 versions are illustrative. Ship compiled descriptors and declarations; consumers need both. This is package delivery, not service discovery.
1. Describe what Inventory offers
This excerpt names one RPC in our shared shop.v1 contract:
service InventoryService {
rpc CheckStock(CheckStockRequest) returns (CheckStockResponse);
}The complete proto also defines CheckStockRequest with sku and CheckStockResponse with units.
2. Generate the known-service map
The catalog plugin reads the proto inputs you give Buf. Alongside generated messages, it emits catalog.gen.ts. Its runtime map contains descriptors:
export const serviceCatalog = {
'shop.v1.OrdersService': OrdersService,
'shop.v1.InventoryService': InventoryService,
} as const;The same generated module also tells TypeScript which method keys and request/response types belong together. Import it into the application and pass its value to the server:
createServer({
catalog: serviceCatalog,
services: [orders, inventory],
});The services input supplies the implementations; the catalog supplies contract knowledge.
3. Call the known contract
Inside the Orders handler:
const stock = await ctx.call(
'shop.v1.InventoryService/CheckStock',
create(CheckStockRequestSchema, { sku: req.sku }),
);The method key uses the proto name CheckStock, not the handler name checkStock. TypeScript knows the request shape and that stock.units is a number. The full message created here also works with catalog generators before 1.3.0.
Knowing a contract is not finding a server
The catalog has no deployment addresses. services mounts local handlers; a remoteResolver supplies a transport for a non-local target. A catalog entry alone neither starts a service nor makes it reachable.
- Watch how the route is chosen.
- Run the same Orders → Inventory example.
- Configure generation, manual catalogs and type imports.
- Let external tools inspect a running server's API.
Full catalog topics and earlier section links
Defining services
Register handlers or initialize them lazily.
Configuring the catalog
Create, freeze and merge runtime catalogs.
Generating the catalog
Buf plugins, runtime descriptors and TypeScript augmentations. Both the generated types and runtime catalog must reach the consumer.
Unary calls — ctx.call
Request types, procedure keys and dispatch.
Streaming calls — ctx.stream
Server, client and bidirectional streams.
Cascade behaviour (CallOptions)
Signals, inherited deadlines and explicit overrides.
Header propagation
Explicit header forwarding; no implicit credential propagation.
Outgoing interceptors
One application policy chain across routes.
Single-image, multiple roles — enabledServices
Select locally mounted services and check version boundaries.
Calling from outside a handler
Server clients and standalone catalog clients.
Error model
Configuration failures, failed calls and route caching.
Related
Complete catalog configuration and behavior.
Watch one call
The routing illustration now lives with Remote Resolvers.
Three pieces. Different jobs.
Contracts
The catalog describes known contracts.
Implementations
services supplies local handlers.
Routes
remoteResolver supplies remote transports.
Run the same example
All six files and the run commands.
package.json
Example dependencies and scripts.
buf.yaml
buf.gen.yaml
tsconfig.json
Example TypeScript configuration.
proto/shop/v1/catalog.proto
Complete Orders and Inventory contract.
src/server.ts
Complete handler and server implementations.
Check the result
Move Inventory out
Run the services in separate processes.