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

@e22m4u/js-service

v0.6.2

Published

Реализация принципа инверсии управления для JavaScript

Readme

@e22m4u/js-service

npm version license

Модуль реализует принцип инверсии управления (Inversion of Control), через паттерн Service Locator в связке с DI-контейнером. Встроенные классы данного модуля берут на себя ответственность за создание, хранение и жизненный цикл объектов, освобождая зависимости приложения от жестких связей и ручного вызова конструкторов.

Содержание

Установка

npm install @e22m4u/js-service

Модуль поддерживает ESM и CommonJS стандарты.

ESM

import {Service} from '@e22m4u/js-service';

CommonJS

const {Service} = require('@e22m4u/js-service');

Описание

Модуль экспортирует два основных класса ServiceContainer и Service, которые можно использовать как по отдельности, так и вместе для построения слабосвязанной архитектуры.

  • ServiceContainer (IoC-контейнер)
    Реализация сервис-контейнера для хранения и разрешения зависимостей.

  • Service (базовый класс для наследования сервисами)
    Инкапсулирует работу с сервис-контейнером, предоставляя наследуемым от него сервисам простой интерфейс для доступа к зависимостям.

Дополнительно:

  • DebuggableService (базовый Service + инструменты логирования)
    Расширенная версия класса Service с дополнительным функционалом для логирования.

Базовые примеры

Создание контейнера и экземпляра сервиса по принципу «одиночки».

import {ServiceContainer} from '@e22m4u/js-service';

class LoggerService {
  log(message) {
    console.log(`[LOG]: ${message}`);
  }
}

const container = new ServiceContainer();

const logger1 = container.get(LoggerService); // создание и кэширование экземпляра
const logger2 = container.get(LoggerService); // возврат существующего экземпляра

console.log(logger1 === logger2); // true

Использование сервиса внутри другого как зависимость.

import {Service} from '@e22m4u/js-service';
import {ServiceContainer} from '@e22m4u/js-service';

// сервис логирования
class LoggerService {
  log(message) {
    console.log(`[LOG]: ${message}`);
  }
}

// сервис калькуляции
class CalculatorService extends Service {
  // так как для работы данного сервиса требуется другой сервис,
  // выполняется наследование класса Service, чтобы иметь доступ
  // к методу getService, через который запрашиваются зависимости
  add(a, b) {
    const logger = this.getService(LoggerService); // <= зависимость
    // при первом обращении к сервису LoggerService создается
    // новый экземпляр, который возвращается при повторном доступе
    const result = a + b;
    logger.log(`${a} + ${b} = ${result}`);
    return result;
  }
}

// создание экземпляра и вызов метода
const calculator = new CalculatorService();
calculator.add(4, 6);
// [LOG]: 4 + 6 = 10

// альтернативный способ (явное создание контейнера)
//   const container = new ServiceContainer();
//   const calculator = container.get(CalculatorService);
//   calculator.add(4, 6);

Сервис как точка входа приложения.

import {Service} from '@e22m4u/js-service';

// сервис логирования
class LoggerService {
  log(message) {
    console.log(`[LOG]: ${message}`);
  }
}

// сервис пользователей
class UserService extends Service { // наследование метода getService
  findUserById(id) {
    const logger = this.getService(LoggerService); // <= зависимость
    logger.log(`Finding user by id ${id}`);
    
    const user = {id, name: 'Jane Doe'};
    logger.log(`Found user with name "${user.name}".`);
    return user;
  }
}

// приложение (точка входа)
class App extends Service { // наследование метода getService
  start() {
    const logger = this.getService(LoggerService); // <= зависимость
    logger.log('Starting App...');
    
    const userService = this.getService(UserService);
    const user = userService.findUserById(123);
    
    logger.log('Done.');
  }
}

// создание экземпляра из запуск приложения
const app = new App();
app.start();

// альтернативный способ (явное создание контейнера)
//   const container = new ServiceContainer();
//   const app = container.get(App);
//   app.start();

Подмена сервиса в контейнере.

import {ApiService} from './api-service';
import {MockApiService} from './mock-api-service';
import {ServiceContainer} from '@e22m4u/js-service';

const container = new ServiceContainer();
// подмена реализации ApiService
container.set(ApiService, new MockApiService());

// любой сервис, который запросит ApiService
// из этого контейнера, получит MockApiService

// MyService зависит от ApiService
const myService = container.get(MyService);

ServiceContainer

В роли IoC-контейнера выступает класс ServiceContainer. Он отвечает за регистрацию, создание и предоставление экземпляров сервисов (зависимостей).

Методы:

  • get(ctor, ...args) получить существующий или новый экземпляр;
  • getRegistered(ctor, ...args) получить существующий или новый экземпляр, только если указанный конструктор зарегистрирован в контейнере, в противном случае выбрасывается ошибка;
  • has(ctor) проверить существование конструктора в контейнере;
  • add(ctor, ...args) добавить конструктор в контейнер (ленивая инициализация);
  • use(ctor, ...args) добавить конструктор и сразу создать экземпляр;
  • set(ctor, service) добавить конструктор и связанный с ним готовый экземпляр;
  • getParent() получить родительский сервис-контейнер;
  • hasParent() проверить наличие родительского сервис-контейнера;

В сигнатурах методов используется вспомогательный тип конструктора:

/**
 * Конструктор класса.
 */
interface Constructor<T extends object = object> {
  new (...args: any[]): T;
}

serviceContainer.get

Метод get класса ServiceContainer создает экземпляр полученного конструктора и сохраняет его для последующих обращений по принципу "одиночки" (Singleton).

Сигнатура:

/**
 * Получить существующий или новый экземпляр.
 *
 * @param ctor
 * @param args
 */
get<T extends object>(ctor: Constructor<T>, ...args: any[]): T;

Пример:

import {ServiceContainer} from '@e22m4u/js-service';

// создание контейнера
const container = new ServiceContainer();

// в качестве сервиса используется класс Date (как пример)
const myDate1 = container.get(Date); // создает и кэширует экземпляр
const myDate2 = container.get(Date); // возвращает существующий экземпляр

console.log(myDate1 === myDate2); // true

Метод get может принимать аргументы конструктора. При этом, если контейнер уже имеет экземпляр данного конструктора, то он будет пересоздан с новыми аргументами.

Пример:

const myDate1 = container.get(Date, '2025-01-01'); // создание экземпляра
const myDate2 = container.get(Date);               // возврат существующего
const myDate3 = container.get(Date, '2030-05-05'); // пересоздание
console.log(myDate1); // Wed Jan 01 2025 03:00:00
console.log(myDate2); // Wed Jan 01 2025 03:00:00
console.log(myDate3); // Sun May 05 2030 03:00:00

serviceContainer.getRegistered

Работает аналогично get, но выбрасывает ошибку, если конструктор сервиса не был предварительно зарегистрирован через add, use или set. Это обеспечивает более строгий контроль над зависимостями.

Сигнатура:

/**
 * Получить существующий или новый экземпляр,
 * только если конструктор зарегистрирован.
 *
 * @param ctor
 * @param args
 */
getRegistered<T extends object>(ctor: Constructor<T>, ...args: any[]): T;

Пример:

class RegisteredService {}
class UnregisteredService {}

const container = new ServiceContainer();
container.add(RegisteredService);

// успешный доступ к зарегистрированному сервису
const service = container.getRegistered(RegisteredService);
// следующий вызов выбросит ошибку,
// так как сервис не зарегистрирован
container.getRegistered(UnregisteredService);
// InvalidArgumentError:
// Constructor UnregisteredService is not registered.

serviceContainer.has

Проверяет, зарегистрирован ли конструктор в контейнере (или в одном из его родительских контейнеров). Возвращает true или false.

Сигнатура:

/**
 * Проверить существование конструктора в контейнере.
 *
 * @param ctor
 */
has<T extends object>(ctor: Constructor<T>): boolean;

Пример:

class MyService {}

const container = new ServiceContainer();
console.log(container.has(MyService)); // false

container.add(MyService);
console.log(container.has(MyService)); // true

serviceContainer.add

Регистрирует конструктор в контейнере, но не создает экземпляр в момент вызова. Экземпляр будет создан только при первом доступе к сервису. Метод позволяет указать аргументы, которые будут использованы для создания экземпляра.

Сигнатура:

/**
 * Добавить конструктор в контейнер.
 *
 * @param ctor
 * @param args
 */
add<T extends object>(ctor: Constructor<T>, ...args: any[]): this;

Пример:

class MyService {
  constructor(name) {
    console.log('MyService instantiated!');
    console.log(`Hello ${name}!`);
  }
}

const container = new ServiceContainer();

console.log('Before add');
container.add(MyService, 'World'); // регистрация, конструктор еще не вызван
console.log('Before get');
const service = container.get(MyService); // создание экземпляра

// Before add
// Before get
// MyService instantiated!
// Hello World!

Аргументы, переданные в add, будут использованы при создании экземпляра, если get будет вызван без аргументов.

serviceContainer.use

Немедленно создает и кэширует экземпляр сервиса. Может использоваться, когда сервис должен быть проинициализирован сразу при настройке другого компонента.

Сигнатура:

/**
 * Добавить конструктор и создать экземпляр.
 *
 * @param ctor
 * @param args
 */
use<T extends object>(ctor: Constructor<T>, ...args: any[]): this;

Пример:

class MyService {
  constructor(name) {
    console.log('MyService instantiated!');
    console.log(`Hello ${name}!`);
  }
}

const container = new ServiceContainer();

console.log('Before use');
container.use(MyService, 'World'); // создание экземпляра
console.log('Before get');
const service = container.get(MyService); // извлечение экземпляр

// Before use
// MyService instantiated!
// Hello World!
// Before get

serviceContainer.set

Метод позволяет связать конструктор с уже существующим экземпляром. Может быть использован для подмены зависимостей в тестах или для внедрения экземпляров, созданных вне контейнера.

Сигнатура:

/**
 * Добавить конструктор и связанный экземпляр.
 *
 * @param ctor
 * @param service
 */
set<T extends object>(ctor: Constructor<T>, service: T): this;

Пример:

class ApiService {}

class MockApiService {
  // имитация реального ApiService
  fetch() {
    return 'mock data';
  }
}

const container = new ServiceContainer();
const mock = new MockApiService();

// установка экземпляра для ApiService
container.set(ApiService, mock);

const api = container.get(ApiService);
console.log(api.fetch()); // "mock data"
console.log(api === mock); // true

serviceContainer.getParent

Метод возвращает родительский контейнер. Если у текущего контейнера нет родителя, то метод выбрасывает ошибку.

Сигнатура:

/**
 * Получить родительский сервис-контейнер или выбросить ошибку.
 */
getParent(): ServiceContainer;

Пример:

const parentContainer = new ServiceContainer();
const childContainer = new ServiceContainer(parentContainer);

// получение ссылки на родительский контейнер
const parent = childContainer.getParent();
console.log(parent === parentContainer); // true

// попытка получить родителя у корневого
// контейнера вызовет ошибку
try {
  parentContainer.getParent();
} catch (error) {
  console.log(error.message);
  // InvalidArgumentError:
  // Service container has no parent.
}

serviceContainer.hasParent

Метод проверяет наличие родительского контейнера и возвращает логическое значение. Данный метод полезен перед вызовом метода getParent, который выбрасывает ошибку при отсутствии родителя.

Сигнатура:

/**
 * Проверить наличие родительского сервис-контейнера.
 */
hasParent(): boolean;

Пример:

const parentContainer = new ServiceContainer();
const childContainer = new ServiceContainer(parentContainer);

console.log(parentContainer.hasParent()); // false
console.log(childContainer.hasParent());  // true

if (childContainer.hasParent()) {
  const parent = childContainer.getParent();
  // логика работы с родителем
}

Иерархия контейнеров

Конструктор ServiceContainer первым параметром принимает родительский контейнер, который используется как альтернативный, если конструктор запрашиваемого сервиса не зарегистрирован в текущем.

class MyService {}

// создание контейнера и регистрация сервиса MyService
const parentContainer = new ServiceContainer();
parentContainer.add(MyService);

// использование созданного ранее контейнера в качестве
// родителя, и проверка наличия сервиса MyService
const childContainer = new ServiceContainer(parentContainer);
const hasService = childContainer.has(MyService);
console.log(hasService); // true

Service

Методы:

  • getService(ctor, ...args) получить существующий или новый экземпляр;
  • getRegisteredService(ctor, ...args) получить существующий или новый экземпляр, только если указанный конструктор зарегистрирован в контейнере, в противном случае выбрасывается ошибка;
  • hasService(ctor) проверить существование конструктора в контейнере;
  • addService(ctor, ...args) добавить конструктор в контейнер;
  • useService(ctor, ...args) добавить конструктор и создать экземпляр;
  • setService(ctor, service) добавить конструктор и его экземпляр;

Сервисом может являться совершенно любой класс. Однако, если это наследник класса Service, то такой сервис позволяет инкапсулировать создание сервис-контейнера, его хранение и передачу другим сервисам.

Пример:

import {Service} from '@e22m4u/js-service';

// сервис Foo
class Foo extends Service {
  method() {
    // доступ к сервису Bar
    const bar = this.getService(Bar);
    // ...
  }
}

// сервис Bar
class Bar extends Service {
  method() {
    // доступ к сервису Foo
    const foo = this.getService(Foo);
    // ...
  }
}

// сервис App (точка входа)
class App extends Service {
  method() {
    // доступ к сервисам Foo и Bar
    const foo = this.getService(Foo);
    const bar = this.getService(Bar);
    // ...
  }
}

const app = new App();

В примере выше мы не заботились о создании контейнера и его передачу между сервисами, так как эта логика инкапсулирована в базовом классе Service.

service.getService

Метод getService обеспечивает существование единственного экземпляра запрашиваемого сервиса, и не создает новый экземпляр при повторных обращениях. Однако, при передаче дополнительных аргументов, сервис будет переопределен с новыми аргументами конструктора.

Сигнатура:

/**
 * Получить существующий или новый экземпляр.
 *
 * @param ctor
 * @param args
 */
getService<T extends object>(ctor: Constructor<T>, ...args: any[]): T;

Пример:

const foo1 = this.getService(Foo, 'arg'); // создание экземпляра
const foo2 = this.getService(Foo);        // возврат существующего
console.log(foo1 === foo2);               // true

const foo3 = this.getService(Foo, 'arg'); // пересоздание экземпляра
const foo4 = this.getService(Foo);        // возврат уже пересозданного
console.log(foo3 === foo4);               // true

service.getRegisteredService

Работает аналогично getService, но выбрасывает ошибку, если конструктор сервиса не был предварительно зарегистрирован, что обеспечивает более строгий контроль над зависимостями.

Сигнатура:

/**
 * Получить существующий или новый экземпляр,
 * только если конструктор зарегистрирован.
 *
 * @param ctor
 * @param args
 */
getRegisteredService<T extends object>(
  ctor: Constructor<T>,
  ...args: any[],
): T;

Пример:

class RegisteredService {}
class UnregisteredService {}

class MyService extends Service {
  run() {
    this.addService(RegisteredService);
    // успешный доступ к зарегистрированному сервису
    const service = this.getRegisteredService(RegisteredService);
    // следующий вызов выбросит ошибку,
    // так как сервис не зарегистрирован
    this.getRegisteredService(UnregisteredService);
    // InvalidArgumentError:
    // Constructor UnregisteredService is not registered.
  }
}

service.hasService

Проверяет, зарегистрирован ли конструктор в контейнере. Возвращает true или false. Полезно для условного запроса зависимостей.

Сигнатура:

/**
 * Проверка существования конструктора в контейнере.
 *
 * @param ctor
 */
hasService<T extends object>(ctor: Constructor<T>): boolean;

Пример:

class OptionalLogger {}

class MyService extends Service {
  log(message) {
    if (this.hasService(OptionalLogger)) {
      const logger = this.getService(OptionalLogger);
      logger.log(message);
    }
  }
}

service.addService

Регистрирует конструктор в контейнере, но не создает экземпляр в момент вызова. Экземпляр будет создан только при первом доступе к сервису. Метод позволяет указать аргументы, которые будут использованы для создания экземпляра.

Сигнатура:

/**
 * Добавить конструктор в контейнер.
 *
 * @param ctor
 * @param args
 */
addService<T extends object>(ctor: Constructor<T>, ...args: any[]): this;

Пример:

class DatabaseService {}
class Config {}

class App extends Service {
  setupDatabase() {
    const config = new Config();
    // регистрация сервиса с аргументами для конструктора
    this.addService(DatabaseService, config);
  }
}

service.useService

Немедленно создает и кэширует экземпляр сервиса. Может использоваться, когда сервис должен быть проинициализирован сразу при настройке другого компонента.

Сигнатура:

/**
 * Добавить конструктор и создать экземпляр.
 *
 * @param ctor
 * @param args
 */
useService<T extends object>(ctor: Constructor<T>, ...args: any[]): this;

Пример:

class Logger {
  constructor() {
    console.log('Logger is ready.');
  }
}

class App extends Service {
  init() {
    // немедленно создает и кэширует экземпляр Logger
    this.useService(Logger); // -> "Logger is ready."
  }
}

service.setService

Метод позволяет связать конструктор с уже существующим экземпляром. Может быть использован для подмены зависимостей в тестах или для внедрения экземпляров, созданных вне контейнера.

Сигнатура:

/**
 * Добавить конструктор и связанный экземпляр.
 *
 * @param ctor
 * @param service
 */
setService<T extends object>(ctor: Constructor<T>, service: T): this;

Пример:

class ApiService {}
class MockApiService {}

class MyComponent extends Service {
  setupForTest() {
    // подмена реального ApiService на его мок-версию
    this.setService(ApiService, new MockApiService());
  }

  fetchData() {
    // следующий вызов вернет экземпляр MockApiService
    const api = this.getService(ApiService);
    return api.fetch();
  }
}

DebuggableService

Данный сервис наследует класс Debuggable и использует композицию для получения функциональности класса Service.
(см. подробнее @e22m4u/js-debug раздел «Класс Debuggable»)

import {apiClient} from './path/to/apiClient';
import {DebuggableService} from '@e22m4u/js-service';

// определение глобального префикса (область имен)
// для отладочных сообщений текущего приложения
process.env['DEBUGGER_NAMESPACE'] = 'myApp';

// переменная DEBUG обычно устанавливается перед
// запуском Node.js процесса, и указывает на область
// имен для логирования, пример: DEBUG=myApp* node .
process.env['DEBUG'] = 'myApp*';

class UserService extends DebuggableService {
  async getUserById(userId) {
    // получение отладчика для данного метода
    // (для каждого вызова генерируется хэш)
    const debug = this.getDebuggerFor(this.getUserById);
    debug('Fetching user with ID %v...', userId);
    try {
      const user = await apiClient.get(`/users/${userId}`);
      debug.inspect('User data received:', user);
      return user;
    } catch (error) {
      debug('Failed to fetch user. Error: %s', error.message);
      throw error;
    }
  }
}

const userService = new UserService();
await userService.getUserById(123);
// myApp:userService:constructor:a4f1 Instantiated.
// myApp:userService:getUserById:b9c2 Fetching user with ID 123...
// myApp:userService:getUserById:b9c2 User data received:
// myApp:userService:getUserById:b9c2   {
// myApp:userService:getUserById:b9c2     id: 123,
// myApp:userService:getUserById:b9c2     name: 'John Doe',
// myApp:userService:getUserById:b9c2     email: '[email protected]'
// myApp:userService:getUserById:b9c2   }

Тесты

npm run test

Лицензия

MIT