Skip to main content

Service Functions

A service function is a plain TypeScript function that receives TServiceParams and returns the service's public API. It is the core unit of composition in Digital Alchemy.

Signature

type ServiceFunction<R = unknown> = (params: TServiceParams) => R | Promise<R>;

Any function matching this shape can be registered as a service. The return type R becomes the service's typed API — what other services see when they access it through TServiceParams.

When it runs

Your service function is called exactly once during wiring — after boilerplate services are ready but before lifecycle stages begin. Any code at the top level of your function runs at that moment:

export function MyService({ logger }: TServiceParams) {
// ← This runs during wiring (top-level code)
const registry = new Map();
logger.trace("wiring MyService");

lifecycle.onReady(() => {
// ← This runs at Ready stage — after all services wired and config validated
logger.info("MyService ready");
});
}

At wiring time:

  • Boilerplate services (logger, config, scheduler, als) are available
  • Other services in the same module may or may not be wired yet (depends on service order / priorityInit)
  • Config values from external sources (env, file) are not yet applied — only defaults are available

In lifecycle callbacks:

  • All services are wired
  • Config is validated (from PostConfig onward)
  • Safe to call other services, open connections, start work

Async service functions

Service functions can be async. The framework awaits the returned Promise before continuing to wire the next service:

export async function DatabaseService({ config }: TServiceParams) {
// async wiring code (unusual — prefer onBootstrap)
const schema = await loadSchema();

return {
query: async (sql: string) => ...,
schema,
};
}
tip

Reserve async wiring for cases where the service's return value genuinely depends on async work. For most connection setup, use lifecycle.onBootstrap() — it's explicitly tied to the right lifecycle stage.

Return types

ReturnBehavior
ObjectBecomes the service API — my_app.service.method()
FunctionBecomes the service API — my_app.service(arg)
void / no returnService has no public API; side-effect-only
Promise<T>Awaited; resolved value becomes the API

See Object Return and Function Return for detailed patterns.

Wiring errors

If a service function throws at the top level (during wiring), the error is treated as fatal. The framework logs it and calls process.exit(1). Use try/catch inside the function if you need to handle initialization errors gracefully, or move the risky code into a lifecycle callback where errors can be surfaced with better context.