npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kiqjs/core

v0.1.2

Published

Core dependency injection container for Node.js/TypeScript, inspired by Spring. Provides @Service, @Configuration, @Bean, @Value, @PostConstruct, autowiring by type, qualifiers, and lifecycle scopes.

Readme

@kiqjs/core

Dependency injection container for Node.js/TypeScript with decorators, YAML configuration, and profile-based component activation

Overview

@kiqjs/core is a lightweight dependency injection (DI) container that brings powerful features to TypeScript applications. It provides a clean, decorator-based API for managing dependencies, configuration, and application lifecycle.

Features

  • Dependency Injection - Constructor and property injection with @Inject decorator
  • Component Scanning - Automatic discovery and registration of components
  • Configuration Management - YAML-based configuration with environment variable support
  • Profile-based Activation - Conditional component registration using @Profile decorator
  • Resource Loading - Load files from resources/ directory (templates, configs, assets)
  • Bean Management - Factory methods with @Configuration and @Bean decorators
  • Lifecycle Hooks - @PostConstruct for initialization logic
  • Type-safe - Full TypeScript support with type inference
  • Zero runtime dependencies - Only requires reflect-metadata and js-yaml

Installation

npm install @kiqjs/core
# or
pnpm add @kiqjs/core
# or
yarn add @kiqjs/core

Quick Start

1. Basic Service with Dependency Injection

import 'reflect-metadata';
import { Service, Inject, Component, runApplication } from '@kiqjs/core';

@Service()
class UserRepository {
  findById(id: string) {
    return { id, name: 'John Doe' };
  }
}

@Service()
class UserService {
  @Inject()
  private userRepository!: UserRepository;

  getUser(id: string) {
    return this.userRepository.findById(id);
  }
}

@Component()
class Application {
  @Inject()
  private userService!: UserService;

  async run() {
    const user = this.userService.getUser('1');
    console.log('User:', user);
  }
}

// Bootstrap
async function bootstrap() {
  const container = await runApplication(Application);
  const app = container.get(Application);
  await app.run();
}

bootstrap();

2. Configuration with @Value

import { Service, Value } from '@kiqjs/core';

@Service()
class EmailService {
  @Value('email.host')
  private host!: string;

  @Value('email.port')
  private port!: number;

  @Value('email.from')
  private from!: string;

  sendEmail(to: string, subject: string, body: string) {
    console.log(`Sending email via ${this.host}:${this.port}`);
    console.log(`From: ${this.from}, To: ${to}`);
  }
}

Configuration file (resources/application.yml):

email:
  host: smtp.gmail.com
  port: 587
  from: [email protected]

3. Profile-based Components

import { Service, Profile } from '@kiqjs/core';

@Service()
@Profile('development')
class DevEmailService {
  sendEmail(to: string, subject: string) {
    console.log(`[DEV] Email to ${to}: ${subject}`);
  }
}

@Service()
@Profile('production')
class ProdEmailService {
  sendEmail(to: string, subject: string) {
    // Send via real SMTP server
  }
}

Configuration (resources/application.yml):

kiq:
  profiles:
    active: development

Core Concepts

Decorators

Component Registration

  • @Component() - Marks a class as a component to be managed by the DI container
  • @Service() - Alias for @Component, semantically represents a service
  • @Repository() - Alias for @Component, semantically represents a repository
  • @Controller() - Alias for @Component, semantically represents a controller
  • @Configuration() - Marks a class as a configuration provider with @Bean methods

Dependency Injection

  • @Inject() - Injects a dependency by type (property injection)
  • @Named(name) - Qualifies a component with a specific name
  • @Qualifier(name) - Alias for @Named

Configuration & Lifecycle

  • @Value(key) - Injects configuration value from YAML or environment variables
  • @PostConstruct() - Marks a method to be called after dependency injection
  • @Profile(profiles) - Conditionally registers component based on active profiles
  • @Bean(name?, scope?) - Declares a factory method in a @Configuration class

Container API

import { Container, scanAndRegister } from '@kiqjs/core';

// Scan and register components
await scanAndRegister(__dirname);

// Create container
const container = new Container();

// Resolve dependencies
const userService = container.get(UserService);
const configInstance = container.get(AppConfiguration);

Application Bootstrap

Using runApplication() (Recommended)

import { Component, Inject, runApplication } from '@kiqjs/core';

@Component()
class Application {
  @Inject()
  private myService!: MyService;

  async run() {
    // Your application logic
  }
}

async function bootstrap() {
  // Automatically scans and registers components
  const container = await runApplication(Application);
  const app = container.get(Application);
  await app.run();
}

bootstrap();

Manual Container Setup

import { Container, scanAndRegister } from '@kiqjs/core';

async function bootstrap() {
  // Scan directory for components
  await scanAndRegister(__dirname);

  // Create container
  const container = new Container();

  // Resolve services
  const service = container.get(MyService);
}

Configuration Management

YAML Configuration Files

Place configuration files in the resources/ directory:

project/
├── resources/
│   ├── application.yml           # Base configuration
│   ├── application-development.yml   # Development overrides
│   └── application-production.yml    # Production overrides
└── src/
    └── ...

Base configuration (resources/application.yml):

kiq:
  profiles:
    active: development

app:
  name: My Application
  version: 1.0.0

database:
  host: localhost
  port: 5432
  name: mydb

email:
  host: smtp.gmail.com
  port: 587

Profile-specific (resources/application-production.yml):

database:
  host: prod-db.example.com
  port: 5432
  name: prod_db

email:
  host: smtp.sendgrid.net

Environment Variables

Environment variables override YAML configuration:

DATABASE_HOST=db.example.com npm start

Configuration lookup order (highest to lowest priority):

  1. Environment variables
  2. Profile-specific YAML (application-{profile}.yml)
  3. Base YAML (application.yml)

Accessing Configuration

import { getConfiguration } from '@kiqjs/core';

const config = getConfiguration();
const dbHost = config.get<string>('database.host');
const dbPort = config.get<number>('database.port', 5432); // with default

See CONFIGURATION.md for detailed documentation.

Profile-based Activation

Profiles allow you to conditionally register components based on the environment:

@Service()
@Profile('development')
class LocalStorageService {
  save(data: any) {
    console.log('Saving to local file system');
  }
}

@Service()
@Profile('production')
class S3StorageService {
  save(data: any) {
    console.log('Saving to S3 bucket');
  }
}

@Service()
@Profile('!production')  // All except production
class DebugLogger {
  log(message: string) {
    console.log('[DEBUG]', message);
  }
}

@Service()
@Profile(['development', 'staging'])  // Multiple profiles
class TestDataSeeder {
  seed() {
    console.log('Seeding test data');
  }
}

Set active profile:

# resources/application.yml
kiq:
  profiles:
    active: development

Or via environment variable:

NODE_ENV=production npm start

See PROFILES.md for detailed documentation.

Resource Loading

Load files from the resources/ directory:

import { ResourceLoader, Inject, Service } from '@kiqjs/core';

@Service()
class EmailService {
  @Inject()
  private resourceLoader!: ResourceLoader;

  sendWelcomeEmail(user: User) {
    // Load HTML template
    const template = this.resourceLoader.getResourceAsString('templates/welcome.html');
    const html = template.replace('{{name}}', user.name);

    // Send email with template
  }

  loadConfig() {
    // Load JSON config
    const config = this.resourceLoader.getResourceAsJson('config/email.json');

    // Load YAML config
    const settings = this.resourceLoader.getResourceAsYaml('config/settings.yml');
  }

  checkTemplateExists() {
    if (this.resourceLoader.exists('templates/welcome.html')) {
      console.log('Template exists');
    }
  }

  listTemplates() {
    const files = this.resourceLoader.listResources('templates');
    console.log('Templates:', files);
  }
}

See RESOURCE-LOADER.md for detailed documentation.

Bean Factory Methods

Use @Configuration and @Bean for complex object creation:

import { Configuration, Bean, Value } from '@kiqjs/core';

@Configuration()
class DatabaseConfig {
  @Value('database.host')
  private host!: string;

  @Value('database.port')
  private port!: number;

  @Value('database.name')
  private database!: string;

  @Bean()
  dataSource() {
    return new DataSource({
      host: this.host,
      port: this.port,
      database: this.database,
    });
  }

  @Bean()
  connectionPool() {
    return new ConnectionPool(this.dataSource());
  }

  @Bean('customRepository', 'singleton')
  userRepository() {
    return new UserRepository(this.connectionPool());
  }
}

// Inject beans anywhere
@Service()
class UserService {
  @Inject('dataSource')
  private dataSource!: DataSource;

  @Inject('customRepository')
  private userRepository!: UserRepository;
}

Lifecycle Hooks

Use @PostConstruct to run initialization logic after dependency injection:

@Service()
class DatabaseService {
  @Inject()
  private config!: DatabaseConfig;

  private connection: any;

  @PostConstruct()
  async init() {
    console.log('Initializing database connection...');
    this.connection = await this.connect();
    console.log('Database connected!');
  }

  private async connect() {
    // Connection logic
  }
}

Scopes

Components can have different lifecycle scopes:

@Component({ scope: 'singleton' })  // Default - one instance
class SingletonService {}

@Component({ scope: 'prototype' })  // New instance per injection
class PrototypeService {}

Advanced Patterns

Optional Dependencies

Use try-catch to handle optional dependencies:

@Component()
class Application {
  @Inject()
  private container!: Container;

  async run() {
    // Try to get optional debug component
    let debugger: DebugComponent | undefined;
    try {
      debugger = this.container.get(DebugComponent);
      debugger.printInfo();
    } catch {
      // DebugComponent not registered (debug profile not active)
    }
  }
}

Named Components

@Service()
@Named('primaryDatabase')
class PrimaryDatabase {}

@Service()
@Named('secondaryDatabase')
class SecondaryDatabase {}

// Inject by name
@Service()
class DataService {
  @Inject('primaryDatabase')
  private primary!: PrimaryDatabase;

  @Inject('secondaryDatabase')
  private secondary!: SecondaryDatabase;
}

Circular Dependencies

The container handles circular dependencies automatically using proxies for property injection:

@Service()
class ServiceA {
  @Inject()
  private serviceB!: ServiceB;
}

@Service()
class ServiceB {
  @Inject()
  private serviceA!: ServiceA;  // Circular dependency - works fine!
}

Examples

CLI Application

See examples/cli-tool for a complete CLI application demonstrating:

  • Dependency injection with @Inject
  • Configuration management with @Value
  • Profile-based components with @Profile
  • Resource loading with ResourceLoader
  • Logging service with configurable levels
  • Data processing with batch support

Web Application

See examples/thread-architecture for a REST API application using @kiqjs/http.

API Reference

Exported Types

import type { Token, Scope, ComponentOptions, ValueSource, Newable } from '@kiqjs/core';

type Token<T = any> = string | symbol | Newable<T>;
type Scope = 'singleton' | 'prototype';

interface ComponentOptions {
  name?: string;
  scope?: Scope;
}

interface ValueSource {
  get(key: string): any;
}

type Newable<T = any> = new (...args: any[]) => T;

Core Functions

// Bootstrap
import { runApplication } from '@kiqjs/core';
const container = await runApplication(AppClass);

// Manual scanning
import { scanAndRegister } from '@kiqjs/core';
await scanAndRegister(__dirname);

// Configuration
import { getConfiguration, ConfigurationLoader } from '@kiqjs/core';
const config = getConfiguration();
const value = config.get<string>('key');

// Resource loading
import { getResourceLoader, ResourceLoader } from '@kiqjs/core';
const loader = getResourceLoader();
const content = loader.getResourceAsString('file.txt');

// Profile checks
import { getActiveProfiles, isProfileActive } from '@kiqjs/core';
const profiles = getActiveProfiles();
const isDev = isProfileActive('development');

// Container
import { Container, GlobalRegistry } from '@kiqjs/core';
const container = new Container();
const instance = container.get(MyService);

Best Practices

  1. Use @Inject() for dependencies - Prefer property injection for cleaner code
  2. Use @Value() for configuration - Keep configuration in YAML files
  3. Use profiles for environment-specific code - Separate dev/prod implementations
  4. Use @Configuration for complex objects - Factory methods for third-party libraries
  5. Use @PostConstruct for initialization - Run setup after dependencies are injected
  6. Use ResourceLoader for file access - Load templates and configs from resources/
  7. Keep services focused - Single responsibility principle
  8. Use semantic decorators - @Service, @Repository for better code clarity

TypeScript Configuration

Enable decorators in your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true
  }
}

Documentation

License

MIT

Contributing

Contributions are welcome! Please open an issue or submit a pull request.