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 🙏

© 2024 – Pkg Stats / Ryan Hefner

gt9p5kz-lib

v0.0.14

Published

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

Downloads

4

Readme

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

S, Единая ответственность.

class UserService {
    async getUsers() {
        const headers = { 'content-type': 'application/tson' };
        const requestInit = { method: 'POST', headers };
        const url = 'url';
        return await fetch(url, requestInit);
    }
}

Привычная и кажется хорошо написанная функция, но полностью нарушающая принцип единой ответственности и принцип Don't Repeat Yourself.

const headers = {'content-type': 'application/tson'} с точки зрения ООП это const headers = new Headers(…) то есть в функции есть ответственность по инстанциированию класса и не одного. Очень бы хотелось иметь функцию примерно в таком виде:

class UserService {
    constructor(fetcher) {}
    async getUsers() {
        const url = 'url';
        return await this.fetcher.posttsON(url);
    }
}

И не писать миллион раз по всему проекту const headers = {'content-type': 'application/tson'}.

I. Разделение интерфейсов.

class ClassicLogger {
    log() {...}
}
class UserService {
    constructor(private readonly logger: ClassicLogger) {}

    getUser() {
        ...
        this.logger.log()
    }
}

Вполне привычная картина, рабочая но если вдруг нужно поменять логгер, то придется пройтись по всему коду и поменять класс логгера. Наверное хотелось бы иметь что то типо того:

interface ILogger {
    log(): void
}

class UserService {
    constructor(private readonly logger: ILogger) {}

    getUser() {
        ...
        this.logger.log()
    }
}

и в случае замены логгера сделать эту замену только в одном месте. И D это общение верхних пунктов. Принцип делегирования инстанциирований во внешнюю систему и описание ожидания от конкретного инстанциирования интерфейсом.

Первый шаг - это сделать внешнюю систему, которая заберет ответственность за инстанциирование. Это будет делать фреймворк. Второй шаг - это договоренность в команде о:

  • каждый класс или объект или примитив должен импортироваться только ОДИН раз и строго в сторону фреймворка
  • каждый класс должен имплементить один или несколько интерфейсов или расширять класс, который имплементит один или несколько интерфейсов

К библиотеке.

Установка библиотеки:

$ npm install gt9p5kz-lib

потом импортим:

import { Athlete } from 'gt9p5kz-lib';
import type { IUse, IExecutableUse } from 'gt9p5kz-lib';

Базовое применение. Инициализация фреймворка:

import { Athlete } from 'gt9p5kz-lib';

new Athlete().createFramework().start();

Для добавления сущностей во фреймворк необходимо имплементировать интерфейс IUse. Например добавим класс Service:

import { Athlete } from 'gt9p5kz-lib';
import type { IBind, IBindFactory, IUse } from 'gt9p5kz-lib';

interface IService {
    showTime(): void;
}

class Service implements IService {
    public showTime(): void {
        const now = new Date();
        const time = `${now.getHours()}:${now.getMinutes()}`;
        console.log(time);
    }
}

class ServiceUse implements IUse {
    public exportBinds(bindFactory: IBindFactory): IBind[] {
        return [bindFactory.createBind(Service, 'Service')];
    }
}

new Athlete().createFramework().use(new ServiceUse()).start();

Теперь при старте фреймворка создаться инстанс класса Service и его можно будет использовать.

Для того чтобы добавить слушаетля необходимо имплементировать интерфейс IExectableUse. Например добавим интервал:

import { Athlete } from 'gt9p5kz-lib';
import type { IBind, IBindFactory, IContainer, IExecutableUse, IUse } from 'gt9p5kz-lib';

interface IInterval {
    start(fn: Function): void;
}

class Interval implements IInterval {
    public start(fn: Function): void {
        try {
            setInterval(fn, 1000);
        } catch {}
    }
}

class IntervalUse implements IUse, IExecutableUse {
    public exportBinds(bindFactory: IBindFactory): IBind[] {
        return [bindFactory.createBind(Interval, 'Interval')];
    }

    public execute(container: IContainer): void {
        const interval = container.getInstanceByInterface<IInterval>('Interval', 'start');
        if (interval) interval.start(() => console.log('athlete'));
    }
}

new Athlete().createFramework().use(new IntervalUse()).start();

Теперь можно сделать так, чтобы интервал брал функию из сервиса:

import { Athlete } from 'gt9p5kz-lib';
import type { IBind, IBindFactory, IContainer, IExecutableUse, IUse } from 'gt9p5kz-lib';

interface IService {
    showTime(): void;
}

class Service implements IService {
    public showTime(): void {
        const now = new Date();
        const time = `${now.getHours()}:${now.getMinutes()}`;
        console.log(time);
    }
}

interface IInterval {
    start(fn: Function): void;
}

class Interval implements IInterval {
    public start(fn: Function): void {
        try {
            setInterval(fn, 1000);
        } catch {}
    }
}

class ServiceUse implements IUse {
    public exportBinds(bindFactory: IBindFactory): IBind[] {
        return [bindFactory.createBind(Service, 'Service')];
    }
}

class IntervalUse implements IUse, IExecutableUse {
    public exportBinds(bindFactory: IBindFactory): IBind[] {
        return [bindFactory.createBind(Interval, 'Interval')];
    }

    public execute(container: IContainer): void {
        const interval = container.getInstanceByInterface<IInterval>('Interval', 'start');
        const service = container.getInstanceByInterface<IService>('Service', 'showTime');
        if (service && interval) interval.start(service.showTime.bind(service));
    }
}

new Athlete().createFramework().use(new ServiceUse()).use(new IntervalUse()).start();

Теперь зарефакторим логгер:

import { Athlete } from 'gt9p5kz-lib';
import type { IBind, IBindFactory, IContainer, IExecutableUse, IUse } from 'gt9p5kz-lib';

interface ILogger {
    log(str: string): void;
}

class ClassicLogger implements ILogger {
    public log(str: string): void {
        console.log('classic');
        console.log(str);
    }
}

class FastLogger implements ILogger {
    public log(str: string): void {
        process.stdout.write('fast\n');
        process.stdout.write(str + '\n');
    }
}

interface IService {
    showTime(): void;
}

class Service implements IService {
    constructor(private readonly logger: ILogger) {}

    public showTime(): void {
        const now = new Date();
        const time = `${now.getHours()}:${now.getMinutes()}`;
        this.logger.log(time);
    }
}

interface IInterval {
    start(fn: Function): void;
}

class Interval implements IInterval {
    public start(fn: Function): void {
        try {
            setInterval(fn, 1000);
        } catch {}
    }
}

class ClassicLoggerUse implements IUse {
    public exportBinds(bindFactory: IBindFactory): IBind[] {
        return [bindFactory.createBind(ClassicLogger, 'Logger')];
    }
}

class FastLoggerUse implements IUse {
    public exportBinds(bindFactory: IBindFactory): IBind[] {
        return [bindFactory.createBind(FastLogger, 'Logger')];
    }
}

class ServiceUse implements IUse {
    public exportBinds(bindFactory: IBindFactory): IBind[] {
        return [bindFactory.createBind(Service, 'Service', ['Logger'])];
    }
}

class IntervalUse implements IUse, IExecutableUse {
    public exportBinds(bindFactory: IBindFactory): IBind[] {
        return [bindFactory.createBind(Interval, 'Interval')];
    }

    public execute(container: IContainer): void {
        const interval = container.getInstanceByInterface<IInterval>('Interval', 'start');
        const service = container.getInstanceByInterface<IService>('Service', 'showTime');
        if (service && interval) interval.start(service.showTime.bind(service));
    }
}

new Athlete()
    .createFramework()
    .use(new ClassicLoggerUse())
    .use(new ServiceUse())
    .use(new IntervalUse())
    .start();

В случае смены логгера нужно будет только:

new Athlete()
    .createFramework()
    .use(new FastLoggerUse())
    .use(new ServiceUse())
    .use(new IntervalUse())
    .start();

Детали:

Entities IBindFactory['createBind'] в качестве первого аргумента можно передавать любой тип кроме undefined. см SingletonEntityType При этом для класса дополнительно нужно передаваться массив токенов инстансов, которые он ожидает в конструкторе (при необходимости) и строго в правильном порядке.

Singleton / Factory при использовании IBindFactory['createBind'] во фреймворке будет храниться единственный экземпляр класса. Если нужно чтобы инстансов класса было много, нужно использовать метод IBindFactory['createFactoryBind'] Вышеописанное справедливо только для классов. Функцииб объекты и примитивы всегда singleton

IUse Интерфейс для добавления сущностей перед стартом фреймворка IUse['createBinds] имеет второй параметр функии с типом IContainer пока что это бесполезно, так что не тратьте время

IExectableUse Интерфейс для описания поведения, которое необходимо сделать после старта фреймворка. Нужеен так как другой способ запустить слушатель - это прописать запуск в конструктор какого нибудь класса, что делать нехорошо, так как нет уверености что класс инстанциируется посленим

Athlete Единственный класс который предоставляет библиотека. Все остальное - это типы и интерфейсы

JavaScritp полностью поддерживается

FrontEnd Еще не успел попробовать, но в целом специфичного нодовсого там нет. Транспилятор тайпскрипта должен импорты поправлять чтобы везде работало

А где HTTP? фреймворк не httpшный, так что напиши свой модуль - это не сложно :) Свой http модуль я выложу позже