Overview
The resource builder provides a fluent interface for defining long-lived singletons with lifecycle management. Resources can have configuration, dependencies, initialization logic, and disposal logic.
import { r } from "@bluelibs/runner";
const database = r.resource("app.database")
.configSchema<{ connectionString: string }>({ parse: (v) => v })
.init(async ({ config }) => {
const connection = await createConnection(config.connectionString);
return connection;
})
.dispose(async ({ value }) => {
await value.close();
})
.build();
Methods
configSchema() / schema()
Defines the validation schema for resource configuration.
.configSchema<TConfig>(schema: IValidationSchema<TConfig>)
.schema<TConfig>(schema: IValidationSchema<TConfig>) // Alias
schema
IValidationSchema<TConfig>
required
Validation schema with a parse method that validates and transforms config..configSchema<{ port: number; host: string }>({
parse: (config) => {
if (config.port < 1 || config.port > 65535) {
throw new Error('Invalid port');
}
return config;
}
})
Returns: New builder with updated config type
resultSchema()
Defines the validation schema for the initialized resource value.
.resultSchema<TValue>(schema: IValidationSchema<TValue>)
schema
IValidationSchema<TValue>
required
Validation schema applied to the resource’s initialized value.
Returns: New builder with updated value type
init()
Defines the resource initialization logic.
.init<TConfig, TValue>(
fn: (context: ResourceInitContext<TConfig, TDeps>) => Promise<TValue>
)
The initialization function. Receives a context object with:
config - The validated resource configuration
deps - Resolved dependencies
runtime - Runtime instance
logger - Scoped logger
context - Resource-specific context (if defined with .context())
.init(async ({ config, deps, logger }) => {
await logger.info('Initializing database');
const connection = await connect(config.connectionString);
return connection;
})
Returns: New builder with init function set
dispose()
Defines the resource cleanup logic.
.dispose(
fn: (context: ResourceDisposeContext<TValue, TDeps>) => Promise<void>
)
The disposal function. Receives a context object with:
value - The initialized resource value
deps - Resolved dependencies
config - Resource configuration
logger - Scoped logger
.dispose(async ({ value, logger }) => {
await logger.info('Closing database connection');
await value.close();
})
Returns: New builder with dispose function set
dependencies()
Defines dependencies to inject into the resource.
.dependencies<TDeps>(
deps: TDeps | ((config: TConfig) => TDeps),
options?: { override?: boolean }
)
deps
DependencyMap | ((config) => DependencyMap)
required
Object mapping dependency keys to resources. Can be static or a function of config..dependencies((config) => ({
logger: globals.resources.logger,
cache: config.enableCache ? cacheResource : null,
}))
When true, replaces existing dependencies. When false (default), merges with existing.
Returns: New builder with updated dependencies type
register()
Registers tasks, events, hooks, middleware, and other resources within this resource.
.register(
items: RegisterableItems | RegisterableItems[] | ((config: TConfig) => RegisterableItems | RegisterableItems[]),
options?: { override?: boolean }
)
items
RegisterableItems | Array | Function
required
Items to register. Can include:
- Tasks (ITask)
- Resources (IResource)
- Events (IEvent)
- Hooks (IHook)
- Middleware (ITaskMiddleware, IResourceMiddleware)
- Tags (ITag)
.register([
greetTask,
databaseResource,
userRegisteredEvent,
sendWelcomeHook,
])
// Or conditionally based on config:
.register((config) => config.enableFeature ? [featureTasks] : [])
When true, replaces existing registrations. When false (default), appends.
Returns: New builder with items registered
exports()
Declares which registered items are visible outside this resource.
.exports(
items: RegisterableItems[],
options?: { override?: boolean }
)
items
RegisterableItems[]
required
Items to make public. When omitted, all registered items are public..register([publicTask, privateTask, internalHelper])
.exports([publicTask])
// Only publicTask is accessible outside this resource
When true, replaces existing exports. When false (default), appends.
Returns: New builder with exports defined
overrides()
Declares overrides to modify registered components.
.overrides(
overrides: OverridableElements[],
options?: { override?: boolean }
)
overrides
OverridableElements[]
required
Array of override definitions..overrides([
r.override().task(someTask).middleware([customMiddleware]).build(),
])
Returns: New builder with overrides registered
middleware()
Attaches middleware to the resource.
.middleware<TMiddleware>(
mw: ResourceMiddlewareAttachment[],
options?: { override?: boolean }
)
mw
ResourceMiddlewareAttachment[]
required
Array of resource middleware attachments..middleware([
globals.middleware.resource.retry.with({ maxAttempts: 3 }),
globals.middleware.resource.timeout.with({ ms: 10000 }),
])
Returns: New builder with middleware attached
Attaches tags for grouping and filtering.
.tags<TTags>(
tags: TagType[],
options?: { override?: boolean }
)
Array of tag definitions.
Returns: New builder with tags attached
context()
Defines a context factory for resource-specific state.
.context<TContext>(factory: () => TContext)
Factory function that creates the context object..context(() => ({
connections: new Map(),
stats: { requests: 0 },
}))
The context is accessible in init() and dispose().
Returns: New builder with context defined
Attaches metadata for documentation and tooling.
.meta<TMeta>(metadata: IResourceMeta)
Metadata object with optional title and description.
Returns: New builder with metadata attached
throws()
Documents which errors the resource initialization may throw.
.throws(errorList: ThrowsList)
Returns: New builder with error documentation
build()
Builds and returns the final resource definition.
.build(): IResource<TConfig, TValue, TDeps, TContext, TMeta, TTags, TMiddleware>
Returns: Immutable resource definition
with()
Attaches configuration to the resource (called on the built resource, not the builder).
const configuredResource = resource.with(config);
Configuration object matching the resource’s config schema.const server = serverResource.with({ port: 3000, host: 'localhost' });
const runtime = await run(server);
Returns: IResourceWithConfig<TConfig, TValue>
Type Signature
interface ResourceFluentBuilder<
TConfig = void,
TValue extends Promise<any> = Promise<any>,
TDeps extends DependencyMapType = {},
TContext = any,
TMeta extends IResourceMeta = IResourceMeta,
TTags extends TagType[] = TagType[],
TMiddleware extends ResourceMiddlewareAttachmentType[] = ResourceMiddlewareAttachmentType[]
> {
id: string;
configSchema<TNewConfig>(schema: IValidationSchema<TNewConfig>): ResourceFluentBuilder<...>;
schema<TNewConfig>(schema: IValidationSchema<TNewConfig>): ResourceFluentBuilder<...>;
resultSchema<TResolved>(schema: IValidationSchema<TResolved>): ResourceFluentBuilder<...>;
init<TNewConfig, TNewValue>(fn: ResourceInitFn<...>): ResourceFluentBuilder<...>;
dispose(fn: ResourceDisposeFn<...>): ResourceFluentBuilder<...>;
dependencies<TNewDeps>(deps: TNewDeps | ((config) => TNewDeps), options?: { override?: boolean }): ResourceFluentBuilder<...>;
register(items: RegisterableItems | RegisterableItems[] | Function, options?: { override?: boolean }): ResourceFluentBuilder<...>;
exports(items: RegisterableItems[], options?: { override?: boolean }): ResourceFluentBuilder<...>;
overrides(overrides: OverridableElements[], options?: { override?: boolean }): ResourceFluentBuilder<...>;
middleware<TNewMw>(mw: TNewMw, options?: { override?: boolean }): ResourceFluentBuilder<...>;
tags<TNewTags>(tags: TNewTags, options?: { override?: boolean }): ResourceFluentBuilder<...>;
context<TNewContext>(factory: () => TNewContext): ResourceFluentBuilder<...>;
meta<TNewMeta>(meta: TNewMeta): ResourceFluentBuilder<...>;
throws(list: ThrowsList): ResourceFluentBuilder<...>;
build(): IResource<TConfig, TValue, TDeps, TContext, TMeta, TTags, TMiddleware>;
}
Examples
Basic Resource
const logger = r.resource("app.logger")
.init(async () => {
return new Logger();
})
.build();
Resource with Configuration
const server = r.resource("app.server")
.configSchema<{ port: number; host: string }>({
parse: (v) => v
})
.init(async ({ config, logger }) => {
await logger.info(`Starting server on ${config.host}:${config.port}`);
const server = await startServer(config);
return server;
})
.dispose(async ({ value, logger }) => {
await logger.info('Shutting down server');
await value.close();
})
.build();
const runtime = await run(server.with({ port: 3000, host: 'localhost' }));
Resource with Dependencies
const emailService = r.resource("app.emailService")
.configSchema<{ apiKey: string }>({ parse: (v) => v })
.dependencies(() => ({
logger: globals.resources.logger,
httpClient: httpClientResource,
}))
.init(async ({ config, deps, logger }) => {
await logger.info('Initializing email service');
return new EmailService(config.apiKey, deps.httpClient);
})
.build();
Application Resource with Registrations
const app = r.resource("app")
.register([
// Resources
databaseResource,
cacheResource,
emailService,
// Tasks
createUserTask,
sendEmailTask,
processOrderTask,
// Events
userRegisteredEvent,
orderProcessedEvent,
// Hooks
sendWelcomeEmailHook,
updateMetricsHook,
])
.init(async ({ logger }) => {
await logger.info('Application initialized');
return { version: '1.0.0' };
})
.build();
const runtime = await run(app);
Resource with Exports (Encapsulation)
const authModule = r.resource("app.modules.auth")
.register([
// Public API
loginTask,
logoutTask,
// Internal implementation
hashPasswordTask,
validateTokenTask,
jwtService,
])
.exports([
loginTask,
logoutTask,
// Internal tasks not exported
])
.build();
Resource with Context
const connectionPool = r.resource("app.connectionPool")
.context(() => ({
connections: new Map<string, Connection>(),
stats: { active: 0, total: 0 },
}))
.init(async ({ config, context, logger }) => {
await logger.info('Initializing connection pool');
return {
getConnection: (id: string) => context.connections.get(id),
createConnection: async (id: string) => {
const conn = await createConnection();
context.connections.set(id, conn);
context.stats.total++;
return conn;
},
};
})
.dispose(async ({ context, logger }) => {
await logger.info(`Closing ${context.connections.size} connections`);
for (const conn of context.connections.values()) {
await conn.close();
}
})
.build();