Skip to main content

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>
)
fn
Function
required
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>
)
fn
Function
required
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,
}))
options.override
boolean
default:"false"
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] : [])
options.override
boolean
default:"false"
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
options.override
boolean
default:"false"
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

tags()

Attaches tags for grouping and filtering.
.tags<TTags>(
  tags: TagType[],
  options?: { override?: boolean }
)
tags
TagType[]
required
Array of tag definitions.
Returns: New builder with tags attached

context()

Defines a context factory for resource-specific state.
.context<TContext>(factory: () => TContext)
factory
() => TContext
required
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

meta()

Attaches metadata for documentation and tooling.
.meta<TMeta>(metadata: IResourceMeta)
metadata
IResourceMeta
required
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);
config
TConfig
required
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();

Build docs developers (and LLMs) love