Skip to main content

Building a Library

A library is a reusable set of services that can be shared across multiple applications. It's created with CreateLibrary instead of CreateApplication. The API is almost identical — the main differences are how dependencies are declared and how the framework loads it.

CreateLibrary vs CreateApplication​

CreateApplicationCreateLibrary
Has bootstrap()✅❌
Has configuration✅✅
Has services✅✅
Has depends❌✅
Can be in libraries array❌✅

Writing a library​

src/my-lib/index.mts
import { CreateLibrary } from "@digital-alchemy/core";
import { CacheService } from "./cache.service.mts";
import { HttpService } from "./http.service.mts";

export const MY_LIB = CreateLibrary({
name: "my_lib",
configuration: {
BASE_URL: {
type: "string",
required: true,
},
CACHE_TTL_MS: {
type: "number",
default: 5000,
},
},
services: {
cache: CacheService,
http: HttpService,
},
});

declare module "@digital-alchemy/core" {
export interface LoadedModules {
my_lib: typeof MY_LIB;
}
}

The declare module augmentation lives in the library's own index file. Any application that imports this library gets the type for free.

Using a library in an application​

Add the library to the application's libraries array:

src/application.mts
import { CreateApplication } from "@digital-alchemy/core";
import { MY_LIB } from "./my-lib/index.mts";
import { AppService } from "./app.service.mts";

export const MY_APP = CreateApplication({
name: "my_app",
libraries: [MY_LIB],
services: {
app: AppService,
},
});

declare module "@digital-alchemy/core" {
export interface LoadedModules {
my_app: typeof MY_APP;
}
}

The libraries array tells the framework which libraries to load and establishes the load order. Libraries are always wired before the application's services.

Accessing library services​

Library services are available on TServiceParams under the library's name:

export function AppService({ my_lib }: TServiceParams) {
lifecycle.onBootstrap(async () => {
const data = await my_lib.http.get("/items");
// my_lib.cache, my_lib.http — fully typed
});
}

Library dependencies​

If your library depends on another library, declare it in depends:

export const MY_LIB = CreateLibrary({
name: "my_lib",
depends: [OTHER_LIB], // OTHER_LIB must also be in the app's libraries array
services: { ... },
});

depends does two things:

  1. Tells the framework to wire OTHER_LIB before MY_LIB
  2. Causes a MISSING_DEPENDENCY error at boot if the application hasn't included OTHER_LIB in its libraries array

Use optionalDepends for soft dependencies — the library won't fail if the dependency is absent, but it will be wired first if it is present:

export const MY_LIB = CreateLibrary({
name: "my_lib",
optionalDepends: [OPTIONAL_LIB],
services: { ... },
});
Library names must match LoadedModules keys

The name in CreateLibrary must exactly match the key you use in LoadedModules. If name: "my_lib" but LoadedModules has myLib, TypeScript won't connect them — my_lib on TServiceParams will be unknown.

Configuration scoping​

Config is module-scoped. A library's config entries live under config.my_lib.*, completely separate from config.my_app.*. Applications can't accidentally override a library's config with a same-named key.

Environment variables follow the same pattern: MY_LIB__BASE_URL=https://api.example.com.

Full dependency graph reference​

For the topological sort algorithm, cycle detection (BAD_SORT), and missing dependency errors, see Dependency Graph.

Next: Testing Basics →