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

mkrtcjs-core

v2.0.0

Published

Core package for MkrtcJS — includes dependency injection, metadata handling, and base architecture utilities.

Readme

Быстрые ссылки

Философия

MkrtcJS - это фреймворк над фреймворком nextjs. Проект вдохновлен такими фреймворками как nestjs и angularjs и так же как они использует декларативный подход в своей основе, только без излишеств.

MkrtcJS - предлагает использовать пяти ступенчатый архитектурный подход. Где:

  • Нулевой уровень - это провайдеры. Пример: (HttpProvider, CacheProvider, и пр.). Провайдеры это кассы нулевого уровня. Провайдеры своего рода каркас проекта. Провйдеры помечаются декоратором @Injectable() что даст другим(сервисы, сущности, репозитории) возможность внедрят в себя провайдер.

  • Первый уровень - это репозитории для общения с API. Все репозитории помечаются декоратором @Repository().Репозитории могут внедрять в себя провайдеров с помощью декоратора @Inject().

  • Второй уровень - это сущности описывающие конкретные сущности. Пример: (UserEntity, ProductEntity, и пр.). Все сущности помечаются декоратором @Entity(). Все сущности так же как и репозитории могут внедрять в себя провадйеров и репозиториев.

  • Третий уровень - это сервисы. Сервисы обычно создаются рядом с компонентом, и служат "головой" компонента. Сервисы отвечают за всю логику компонента, а так же управляют реактивным состоянием с помощью декораторов @State() и @UseState. Сервисы внутри себя могут внедрять как провайдеров так и репозиториев.

  • Четвёртый уровень - это компоненты. В mkrtcjs компоненты максимально тупые(в хорошем смысле) и не реализуют никакую логику. Любой компонент использует в себя hook useService(Service) для получения реактивного состояния а так же сервис для обработки клиентских событий(клик, ввод и пр.)

Установка

npm i mkrtcjs-core

Подключение

Чтобы обеспечить максимальную совместимость с серверными декораторами, советуем сразу из middleware.ts вернуть bootstrap. Это даст возможность на стороне сервера использовать @Req() декоратор.

// middleware.ts
import { bootstrap } from "mkrtcjs-core";
import { NextRequest } from "next/server";


export async function middleware(req: NextRequest){
    return await bootstrap(req);
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
}

Чтобы обеспечить связь между серверной и клиентской частью приложения, оберните все что внутри <body></body> компонентом <MkrtcRootProvider />.

// layout.tsx
import { MkrtcRootProvider } from "mkrtcjs-core";

export default function RootLayout({children}: Readonly<{children: React.ReactNode;}>) {
  return (
    <html lang="en">
      <body>
        <MkrtcRootProvider>
            {children}
        </MkrtcRootProvider>
      </body>
    </html>
  );
}

Примеры

Клиентские декораторы

@Service()

@Service - это декоратор класса, который создаёт из обычного класса сервис.

Использование:

import { Service } from "mkrtcjs-core/client";

@Service(options)
export class MyService {}

Аргументы:

Декоратор @Service() позволяет объявить/обновить реактивное состояние, следить за изменениями реактивного состоянии а так же внедрять в себя репозитории и провайдеры.

Пример:

// ./services/my.service.ts
import { Service, State, UseStateFactory } from "mkrtcjs-core/client"
import { OnInit, Inject } from "mkrtcjs-core";
import { UserRepository } from "@Repositories";

export interface MyServiceState{
    user: UserEntity | null;
    loading: boolean;
}

const UseMyServiceState = UseStateFactory.create<MyService, MyServiceState>();

@Service()
export class MyService implements MyServiceState{
    @State<UserEntity | null>(null)
    public user: UserEntity | null;

    @State<boolean>(false)
    public loading: boolean;

    @Inject(UserRepository)
    private userRepo!: UserRepository;

    @OnInit()
    private _onInit(){
        this.getUser();
    }

    @UseMyServiceState.return("user")
    @UseMyServiceState.autoToggle("loading")
    private getUser(){
        return this.userRepo.init();
    }
}

Использование в компоненте:

// ./my.component.tsx
"use client"
import { useService } extends "mkrtcjs-core/client";
import { MyService, MyServiceState } from "./services/my.service";

export const MyComponent = () => {
    const [service, {user, loading}] = useService<MyService, MyServiceState>(MyService, ["user", "loading"]);

    if(loading) return <div>...loading</div>;

    return (
        {user && <span>{user.username}</span>}
    )
}

ВАЖНО: Обязательно импортируйте useService из mkrtcjs-core/client. Не передавайте в useService конструктор класса не обернутый в декоратор @Service()


@InjectService()

Декоратор @InjectService() - Внедрят другой сервис в совйство сервиса.

Использование

import {InjectService} from "mkrtcjs-core/client"

@InjectService(scope?: string | null, options?: InjectServiceOptions)
private readonly userService: UserService;

Аргументы

  • scope?: string | null - Название инициализированного сервиса. Например еси в другом компоенте вы сделали useService(MyService, [...state], {scope: "service1"}) и сейчас хотите внедрить именно этот сервис, то вам необхоимо использовать @InjectService('service1')
  • options?: InjectServiceOptions - InjectServiceOptions

Пример

// my-another.service.ts
import {Service} from "mkrtcjs-core/client"

@Service()
export class MyAnotherService{
    ...
}
// my.service.ts
import {Service, InjectService} from "mkrtcjs-core/client"

@Service()
export class MyService{
    @InjectService()
    private readonly myAnotherService: MyAnotherService;
}

@State()

@State() - Говорит сервису что данное свйоство касса является реактивым состоянием, и необходимо перерисовать компанент каждый раз, когда данное свойство меняется

использование:

import { State } from "mkrtcjs-core/client";
@State(initialValue, options)
public user: UserEntity | null;

Параметры:

  1. initialValue?: T - значение по умолчанию. default = null
  2. options?: - StateOptions

Пример:

import { Service, State } from "mkrtcjs-core/client";

interface MyServiceState{
    loading: boolean;
}

@Service()
export class MyService implements MyServiceState{
    @State<boolean>(false)
    public loading: boolean;
}

@UseState

Декоратор @UseState - позволяет гибко управлять реактивным состоянием. Вообще @UseState сам по себе не декоратор, а обычный объект, декоратором являются его методы, которых аж 7 штук.

ВАЖНО: Любой метод, который будет обернут декоратором @UseState будет возвращать Promise.

И так по порядку, сначала создадим 3 свойства с реактивным состоянием

@State<UserEntity | null>(null)
public user: UserEntity | null;

@State<boolean>(false)
public loading: boolean;

@State<number>(0)
public counter: number;

И так, как нам взаимодействовать с состоянием?

Для начала создадим декоратор с помощью фабрики UseStateFactory. Это позволит каждый раз не передавать тип сервиса и реактивного состояния в @UseState.

import { UseStateFactory } from "mkrtcjs-core/client";

const UseUserState = UseStateFactory.create<UserService, UserServiceState>();

Отличие UseUserState от обычного UseState в том, что первый уже типизирован под наш сервис.

И так, теперь рассмотрим какие методы нам дает @UseState() и как с их помощь менять реактивное состояние.

  1. @UseService.return(key) - как уже понятно из названия, кладёт в реактивное состояние key возвращаемое значение метода. Исполльзование

    @UseState.return(key) // key = "hello world"
    public myMethod(){
        return "hello world" 
    }

    Параметры:

    • key - Название свойства куда нужно положить значение

    Пример:

     @UseState.return("user")
     public initUser(){
         return this.userRepo.init();
     }
  2. @UseState.before(key, updater) - Меняет рекативное состояние до вызова метода.

    Пример:

     @UseState.before("loading", () => true)
     public initUser(){
         return this.userRepo.init();
     }

    Параметры:

    • key: string - Название ключа куда нужно положить значение
    • updater: Updater - callback который должен возвращать новое значение
  3. @UseState.after(key, afterUpdater) - Меняет рекативное состояние после вызова метода.

    Пример:

     @UseState.after("loading", () => false)
     @UseState.after("user", (current, returnValue) => returnValue)
     public initUser(){
         return this.userRepo.init();
     }

    Параметры:

    • key: string - Название ключа куда нужно положить значение
    • updater: AfterUpdater - callback который должен возвращать новое значение
  4. Методы @UseState.increment(key) и @UseState.decrement(key) - название уже говорит само за себя: increment увеличивает значение реактивного состояния на 1, decrement наоборот уменьшает на 1.

    ВАЖНО! Для того чтобы методы работали, значение реактивного состояния обязательно должен быть Числом, иначе выкинет ошибку

    Пример:

    // +1
    @UseState.increment("count")
    public addNumber(){}
    // -1
    @UseState.decrement("count")
    public subtractNumber(){}

    Параметры:

    • key: string - Ключ свойства
  5. Методы @UseState.toggle(key) и @UseState.autoToggle(key) - переключатель boolean значений. Отличие их в том, что .toggle() просто переключает 1 раз, а autoToggle - до вызова устанавливает значение свойства на true а после false, удобно для всяких loader-ов.

    Пример:

    @State(false)
    public modalOpen: boolean;
    
    @State(false)
    public loading: boolean;
       
    @UseState.toggle("modalOpen") // modalOpen = true
    @UseState.autoToggle("loading") // loading = before true after false
    public getUser(id: number){
        return this.userRepo.findOnr(id);
    }

    Параметры:

    • key: string - Ключ свойства
  6. Метод @UseState.patch(key) - принимает в аргументы название свойства, и возвращает все методы которые есть у обычного @UseState кроме .patch с тем отличием, что остальным методам больше не нужно указать key.

    Пример:

    @(UseState.patch("loading").autoToggle()) // Если вырражения декоратора состоят более одного метода, нужно весь декоратор обернуть скобакми
    @UseState.return("user")
    public initUser(){
        return this.userRepo.init();
    }

    Параметры:

    • key: string - Ключ свойства

Пример:

@UseUserState.increment("counter") // При каждом вызове, counter += 1
@UseUserState.autoToggle("loading") // до вызова true, после - false
@UseUserState.return("user") // кладем то что вернётся метод, в "user"
public async getUser(id: number){
    return await this.userRepo.findOne(id);
}

@(UseUserState.patch("counter").increment())

// что тут произошло? Мы с помощью дженерика указываем тип аргументов, в виде массив. В аргументы callback функции к нам падают Текущее значение, экземпляр текущего класса(сервиса) И аргументы с дженерик типом в виде массива
@UseUserState.before<[number, string]>("loading", (currentValue, instance, [arg1, arg2]) => true)
@UseUserState.after("loading", () => false)
// Тут вторым дженериком мы указываем тип возвращаемого значения, который после к нам попадет вторым аргументом в updater
@UseUserState.after<[number, string], UserEntity>("user", (cur, rv) => rv)
public testBeforeAfter(arg1: number, arg2: string): UserEntity {
    // code
}

@Watch()

@Watch() - это декоратор, который вызывает метод, при изменении конкретного рекативного свойства.

использование:

import { Watch } from "mkrtcjs-core/client";

@Watch(key)
private watch(key, next, prev){}

Аргументы:

  • key: string | string[] | "*" - название стейтов за которыми нужно следить. Если key = "*" то будет следить за всеми свойствами.

Аргументы в методе:

  • key: string - Ключ стейта на котором произошло изменение.
  • next: T - новое значение стейта
  • prev?: T - старое значение стейта

ВАЖНО: @Watch() будет вызвать метод всегда, даже если в реативное состояние положили одно и то же значение.

ЗАМЕТКА: Старайтесь максимально избегать использования @Watch("*"). Это может повлиять на производительность, особенно если состояний много.

@UseEffect()

@UseEffect работает идентично @Watch() с тем отличием, что @UseEffect будет вызвать метод, только если новое значение реактивного состояния отличается от старого.

@Timer()

Декоратор @Timer() как уже понятно из название создает таймер.

Использование:

import { Timer } from "mkrtcjs-core/client";

@Timer(key, options)
public method(){}
  • key: string; - Название стейта
  • options: TimerOptions - Настройки таймера

Перед использованием @Timer() обязательно обявите реактивное состояние со значением isTimer: true

Пример:

import { State, Timer } from "mkrtcjs-core/client";
import type { ITimer } from "mkrtcjs-core/types";

@State<ITimer | null>(null, {isTimer: true})
public timer: ITimer;

@Timer("timer", {ms: 5000, tickRate: 1000})
public sendSmsCode(timer: Timer, ...args){}

ВАЖНО!!! Не важно какое вы укажите значение по умолчанию для свойства, при использовании @Timer значение свойства будет переопределен на ITimer

@OnPathChange()

Декоратор @OnPathChange - Вызывает метод каждый раз когда url путь меняется. Метод всегда принимает 1 параметр - pathname;

Использование:

import { OnPathChange } from "mkrtcjs-core/client";

@OnPathChange()
private _onPathChange(pathname: string){}

@UseNavigator()

Декоратор @UseNavigator() дает возможность использовать в аргументах метода следующие декораторы:

  • @Router() - Значение из netxjs/useRouter();
  • @Pathname() - Значение из nextjs/usePathname();

Использование:

import { UseNavigator, Router, Pathname } from "mkrtcjs-core/client";

@UseRouter()
public method(@Router() router: AppRouterInstance, @Pathname() pathname: string){}

ВАЖНО: Декоратор @UseNavigator() не будет вызвать метод при каждом изменении url пути. Учтите это при разработке.

Общие декораторы

@Injectable()

Декоратор @Injectable() внутри себя создаёт экземпляр класса и дальше передаётся через декоратор @Inject()

Ограничения:

  1. Класс обернутый декоратором @Injectable() в конструктор может принимать только другие классы обернутые декоратором @Injectable() Советы:
  2. Оберните декоратором Injectable() только провайдеры и репозитории, а так же все остальные классы, которые могут быть Single tone.

Использование:

// ./http-client.ts
import { Injectable } from "mkrtcjs-core";

@Injectable()
export class HttpClient{
  // you code
}

@Inject()

Декоратор @Inject() - внедряет класс, в свойство класса.

ВАЖНО: Конструктор класса который необходимо внедрить, обязательно должен быть обернутым декоратором @Injectable(), иначе внедрение не произойдёт.

Использование:

// ./user.repository.ts
import { Repository, Injectable, Inject } from "mkrtcjs-core";
import { HttpClient } from "@/shared";
import { UserEntity, IUserEntity } from "@/entities";

@Repository()
@Injectable()
export class UserRepository{
   @Inject()
   private readonly httpClient: HttpClient;

   public async findAll(): Promise<UserEntity[]>{
     const users = await this.httpClient.get<IUserEntity>("/user");
     return users.map(user => new UserEntity(user));
   }
}

@Repository()

Декоратор @Repository() - говорит классу о том, что он может в себя внедрять все классы помеченные декоратором @Injectable(). Пример здесь. Может использоваться в связке с декоратором @Injectable().

@Entity()

Декоратор @Entity() - говорит классу о том, что он может в себя внедрять все классы помеченные декоратором @Injectable(). Не может использоваться в связке с декоратором @Injectable().

Пример:

// ./user.entity.ts
import { Entity, Inject } from "mkrtcjs-core";
import type { IUserEntity } from "./user.interface.ts";
import { UserRepository } from "@/repositories"

@Entity()
export class UserEntity implements IUserEntity{
    public readonly id: number;
    public name: string;
    // ...

    @Inject(UserRepository)
    private readonly userRepository: UserRepository;

    constructor(user: IUserEntity){
        Object.assign(this, user);
    }

    public async save(): Promise<UserEntity>{
       return await this.userRepository.update(this.id, this);
    }
}

@OnInit()

Декоратор @OnInit() вызывает метод при инициализации сервиса.

Пример:

import { OnInit } from "mkrtcjs-core";

@OnInit()
private async _onInit(){
    await this.getUsers();
}

Теперь при первом использовании useService() будет вызван _onInit() метод.

ЗАМЕТКА!!! Название метода может быть любым.

ЗАМЕТКА!!! Рекомендуем сделать метод приватным.

ВАЖНО!!! Метод вызовется только при инициализации сервиса. Т.е. если вы в родительском классе внедрили сервис с помощью хука [[#useService()]], а дальше в дочернем компонент тоже пытаетесь внедрить сервис, то инициализация не произойдёт, поскольку сервис уже инициализирован в родительском компоненте, а в дочернем компоненте вы его получите из DI контейнера.

@Catch()

Декоратор @Catch() автоматом оборачивает метод в try/catch.

Использование:

import { Catch } from "mkrtcjs-core";

@Catch<Instance, args[], Error>(handler: CatchHandle<Instance, args[], Error>, options?: CatchOptions)
public method(){
    if(1 !== 2) throw new Error();
}
  • handler: CatchHandle - callback функция, которая отработает в случае ошибки.
  • options?: CatchOptions - Настройки

Хуки

useService()

Хук useService нужен для того, чтобы внедрить сервис в компонент.

Использование

import {useService} from "mkrtcjs-core/client";
useService<ServiceClass, ServiceState>(service: ServiceClass, state: ServiceState, options?: UseServiceOptions)

Аргументы

  • service: ServiceClass - Конструктор сервиса.
  • state: ServiceState - Тип рекативного состояния.
  • options?: UseServiceOptions - UseServiceOptions. Настройки.

Пример

// user.service.ts
import { Service, State, UseStateFactory } from "mkrtcjs-core/client";
import { Inject, OnInit } from "mkrtcjs-core";
import { UserRepository } from "@/repositories"; 
import { UserEntity } from "@/entities";

const UseState = UseStateFactory.create<UserService, UserState>();

export interface UserState{
    loading: boolean;
    users: UserEntity[];
}

export class UserService implements UserState{
    @State(false)
    public loading: boolean;

    @State([])
    public users: UserEntity[];

    @Inject()
    private readonly userRepo: UserRepository;

    @OnInit()
    private _onInit(){
        this.findAllUsers();
    }

    @UseState.autotoggle("loading")
    @UseState.return("users")
    private async findAlllUsers(){
        return await userRepo.findAll();
    }

    @UseState.autotoggle("loading")
    public sayHello(user: UserEntity){
        console.log(`user: ${user.name} say hello`);
    }

}

// use-user-service.hook.ts
import { UseServiceFactory } from "mkrtjs-core/client";
import { UserService, UserState } from "./user.service";

export const useUserService = UseServiceFactory.create<UserService, UserState>(UserService, {isGlobal: false, scope: "alternativeService"});

// UserComponent.tsx
import { useService } from "mkrtcjs-core/client";
import { useUserService } from "./use-user-service.hook";  
import { UserService, UserState } from "./user.service";

export const UserComponent = () => {
    const [service, {users, loading}] = useService<UserService, UserState>(UserService, ["loading", "users"]);
    // or
    const [service, {users, loading}] = useUserService(["loading", "users"]);

    
    return (
        {loading ?
            <div>loading...</div> :
            <ul>
                {users.map(user => (
                    <li onClick={() => service.sayHello(user)} key={user.id}>{user.name}</li>
                ))}
            </ul>

        }
    )
}

Типы

ServiceOptions

interface ServiceOptions {
    isGlobal?: boolean;
}
  • isGlobal?: boolean - Будет ли сервис глобальный или нет. Глобальный сервис не удаляется при демонтировании родительского компонента, либо при отсутствии владельцев. Свойство можно переопределить в useService()

StateOptions

interface StateOptions{
    isTimer?: boolean;
    timerOptions?: StateTimerOptions
}
  • isTimer?: boolean - если true, то @State() будет иметь тип ITimer.
  • timerOptions?: StateTimerOptions - Настройки таймера. Будет работать только если isTimer = true.

StateTimerOptions

interface StateTimerOptions{
    delay?: number;
}
  • delay: number - через сколько миллисекунд запустить таймер.

ITimer

interface ITimer {
    completed: boolean;
    left: number;
    ms: number;
}
  • ms: number - Насколько миллисекунд был запущен таймер.
  • left: number - Сколько миллисекунд осталось до завершения таймера.
  • completed: boolean - Запрещён ли отчёт таймера.

TimerOptions

interface TimerOptions {
    tickRate: number;
    ms: number;
    onTick?: (timer: ITimer) => void;
}
  • tickRate: number; - Раз в сколько ms вызвать ре-рендер компонента.
  • ms: number; - Насколько ms запустить таймер
  • onTick?: (timer: ITimer) => void; - callback который будет вызван каждый tickRate.

Updater

type Updater<S, C, A> = (current: S, args: A, instance: C) => S;
  • current: T - Текущее значение состояния.
  • args: A[] - Аргументы метода.
  • instance: C - Экземпляр текущего сервиса.

AfterUpdater

type AfterUpdater<S, R, C, A> = (current: S, returnValue: R, args: A, instance: C) => S;
  • current: T - Текущее значение состояния.
  • returnValue - Возвращаемое значение метода.
  • args: A[] - Аргументы метода.
  • instance: C - Экземпляр текущего сервиса.

CatchHandle

interface CatchHandle<I, A, E extends Error> {
    instance: I;
    args: A;
    exp: E;
}
  • instance: I; - экземпляр класса где используется декоратор
  • args: A[]; - аргументы метода
  • Error: E; - тип ошибки который может выкинуть метод

CatchOptions

interface CatchOptions {
    rethrow?: boolean;
    useReturn?: boolean;
}
  • useReturn: boolean; - если true, то метод вернет callback при ошибке
  • reThrow: boolean; - если true, то выкинет ошибку наружу

InjectServiceOptions

interface InjectServiceOptions{
    init?: boolean;
}
  • init?: boolean - Укажите true если не уверены, что сервис будет инициализирован до инициаллизации текущего сервиса. Тогда, перед внедреием, пройдет провека и если сервис не будет инициализирован, то произойдет его инициализация.

ВЖНО - Если не указать значение true и при инициализации текущего касса, внедряемый сервис не будет инициализирован, то выкинется ошибка Service [ServiceName] not inited.

UseServiceOptions

interface UseServiceOptions {
    scope?: string;
    isGlobal?: boolean;
}
  • scope?: string - Название сервиса. Укажите если хотите несколько раз использовать useService() и чтобы у всех было свое реактивное состояние. Если укажите, то при использовании декоратора @InjectService необходимо будет первым параметром передать scope.
  • isGlobal?: boolean - Будет ли сервис глобальным. По умолчанию: false.