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

stone-analytics

v0.0.136

Published

analytic package STONE

Readme

Stone Analytics

NPM-пакет клиентской аналитики для сайтов STONE. Собирает поведенческие события, обогащает их контекстом страницы и идентификаторами пользователя, отправляет в Google Tag Manager (window.dataLayer) и на бэкенд stone.ru/api/analytics/send-event.

Оглавление

Зачем нужен

На сайтах STONE (stone.ru, realty, лендинги проектов) нужна единая клиентская аналитика: одни и те же идентификаторы, контекст страницы и формат событий во всех фронтенд-приложениях. Пакет решает четыре ключевые задачи:

  • Идентификация пользователя. Генерирует и поддерживает st_client_id (на год) и st_session_id (на сессию) в localStorage. Сессия сбрасывается при закрытии последней вкладки или после 30 минут неактивности.
  • Обогащение событий контекстом. Автоматически определяет page_type, page_direction, page_project, page_lot по URL. Подключает идентификаторы Яндекс.Метрики, Comagic, FingerprintJS, GA, данные A/B-теста и авторизации.
  • Единый dataLayer для GTM. Все события (pageview, click, submit и др.) пушатся в window.dataLayer в стандартизированном формате — GTM-триггеры обрабатывают их без дублирования логики на каждом сайте.
  • Прямая отправка на бэкенд. Модуль sendAnalyticsToAPI отправляет события на https://stone.ru/api/analytics/send-event (используется внутри пакета и доступен для прямого вызова).

Стек технологий

| Технология | Назначение | | --- | --- | | TypeScript 5 | Типизация исходного кода | | tsup | Сборка CJS/ESM + генерация .d.ts | | MobX 6 | Реактивный стор StoneDataLayerStore | | mobx-react-lite | Интеграция MobX с React | | @fingerprintjs/fingerprintjs | Браузерный fingerprint (st_client_hash_id) | | cookies-next | Чтение cookie GA (_ga) | | js-md5 | Хеширование виртуального номера телефона | | React 18+ | Peer-зависимость: хуки useInitializeMetrics, useComagicStatus |

Архитектура

Принципиальная схема пакета

flowchart TB
    subgraph consumer [Next.js / React приложение]
        AppProvider["AppProvider + useInitializeMetrics"]
        Components["Компоненты сайта"]
    end

    subgraph package [stone-analytics]
        SessionId["sessionClientId"]
        Store["StoneDataLayerStore"]
        PageUtils["pageType / pageDirection / getProjectName / getLotName"]
        Events["eventPageView / eventClick / eventSubmit / ..."]
        Utils["utils: fingerprint, GA, YM, AB cookie"]
        SendAPI["sendAnalyticsToAPI"]
        DataLayerPush["dataLayerPush"]
    end

    subgraph browser [Браузер]
        LS[("localStorage")]
        DL["window.dataLayer"]
        GTM["Google Tag Manager"]
        YM["Яндекс.Метрика"]
        Comagic["Comagic"]
    end

    subgraph backend [Бэкенд STONE]
        API["POST /api/analytics/send-event"]
    end

    AppProvider --> SessionId
    SessionId --> LS
    Components --> Store
    Store --> PageUtils
    Store --> Events
    Events --> Utils
    Events -->|"pushLayer"| DL
    DL --> GTM
    Utils --> YM
    Utils --> Comagic
    SendAPI --> API
    DataLayerPush --> DL

Модули

Пакет состоит из шести логических модулей:

  • Hooks (src/hooks/useInitializeMetrics.ts) — React-хук инициализации st_client_id / st_session_id, управление жизненным циклом сессии (счётчик вкладок, таймер неактивности 30 мин).
  • Session / Client ID (src/utils/sessionClientId.ts) — генерация time-based UID, проверка годового срока st_client_id.
  • StoneDataLayerStore (src/store/StoneDataLayerStore/StoneDataLayerStore.ts) — MobX-стор: хранит контекст страницы, предоставляет методы setEvent* для каждого типа события, пушит в window.dataLayer.
  • Events (src/store/StoneDataLayerStore/events/) — обработчики конкретных событий: формируют payload из getDefaultAnalyticsData + специфичные поля.
  • Page context (src/store/StoneDataLayerStore/utils/) — определение типа страницы, направления, проекта и лота по pathname/href через regex-правила (regs.ts).
  • TransportsendAnalyticsToAPI (HTTP на бэкенд), dataLayerPush (GTM dataLayer), ymTarget (reachGoal в Яндекс.Метрику).

Диаграмма взаимодействия

flowchart LR
    subgraph init [Инициализация]
        Hook["useInitializeMetrics"]
        Hook -->|"setClientId / setSessionId"| LS[localStorage]
        Hook -->|"tabCount, beforeunload"| LS
        Hook -->|"30 min inactivity"| LS
    end

    subgraph event [Отправка события]
        Component -->|"setPageParams"| Store
        Component -->|"setEventClick(...)"| Store
        Store --> GetDefault["getDefaultAnalyticsData"]
        GetDefault --> SetIds["setAnalyticId (YM + Comagic)"]
        SetIds --> Push["pushLayer → dataLayer"]
    end

    subgraph ids [Сбор идентификаторов]
        SetIds --> YM["getYmUid"]
        SetIds --> CM["getComagicVisitorId"]
        GetDefault --> FP["fingerprint_id (store)"]
        GetDefault --> Auth["auth_store / user_reg_id"]
        GetDefault --> AB["NEXT_PUBLIC_AB_TEST_GROUP"]
    end

Ключевые сервисы и утилиты

| Экспорт | Файл | Назначение | | --- | --- | --- | | stoneDataLayerStore | StoneDataLayerStore.ts | Синглтон MobX-стора, точка входа для всех событий | | useInitializeMetrics | useInitializeMetrics.ts | Инициализация ID сессии/клиента в корневом провайдере | | getPageType | pageType.ts | Тип страницы: main, lot_card, project_card, lot_catalog и др. | | getPageDirection | pageDirection.ts | Направление: apartments, offices, retail, rent, investments | | getProjectName | getProjectName.ts | Slug проекта из URL | | getLotName | getLotName.ts | Slug лота из URL | | getStoneHashClientId | utils.ts | FingerprintJS visitor ID | | getStoneClientId / getStoneSessionId | utils.ts | Чтение ID из localStorage | | getGoogleAnalyticsClientId | utils.ts | Client ID из cookie _ga | | getYandexMetrikaClientId | utils.ts | Client ID из cookie _ym_uid | | getABCookie | getABCookie.ts | Чтение A/B cookie по имени | | useComagicStatus | useComagicStatus.ts | React-хук: загружен ли скрипт Comagic | | getVisibleVirtualPn | getVisibleVirtualPn.ts | MD5-хеш виртуального номера из #header_phone | | sendAnalyticsToAPI | sendAnalyticsToAPI.ts | POST на бэкенд (не экспортируется из index.ts, доступен в исходниках) |

Схема хранения данных

Пакет не использует серверную БД. Все персистентные данные хранятся в localStorage браузера и в window.dataLayer.

localStorage

erDiagram
    st_client_id {
        string value "timestamp + random, обновляется раз в год"
    }

    st_session_id {
        string value "timestamp + random, сбрасывается при закрытии последней вкладки или 30 мин неактивности"
    }

    tabCount {
        int value "счётчик открытых вкладок"
    }

    user_reg_id {
        string value "ID зарегистрированного пользователя (читается, не создаётся пакетом)"
    }

    auth_store {
        json value "состояние авторизации { isLoggedIn: boolean }"
    }

    _ga {
        string value "fallback GA client ID"
    }

    _ym_uid {
        string value "fallback YM client ID"
    }

| Ключ | Тип | Жизненный цикл | Описание | | --- | --- | --- | --- | | st_client_id | string | 1 год с момента создания | Уникальный ID клиента. Первые 10 символов — Unix timestamp. Пересоздаётся, если прошёл год (checkTimeStamp) | | st_session_id | string | На сессию | Уникальный ID сессии. Сбрасывается при tabCount === 0 (закрытие последней вкладки) или после 30 мин бездействия | | tabCount | string (int) | На сессию | Счётчик открытых вкладок. Инкрементируется при монтировании useInitializeMetrics, декрементируется в beforeunload | | user_reg_id | string | Управляется приложением | ID пользователя, читается в initializeUserId() | | auth_store | JSON | Управляется приложением | { isLoggedIn: boolean }, читается в initializeUserAuth() |

Формат UID (makeUID.ts): Date.now() + случайное число 100000000–999999999.

window.dataLayer

Массив объектов событий. GTM читает его через триггеры. Пакет пушит через store.pushLayer():

window.dataLayer = window.dataLayer || []
window.dataLayer.push(tagManagerArgs)

Формат события в dataLayer

Каждое событие содержит общие поля из getDefaultAnalyticsData + специфичные поля события:

| Поле | Тип | Источник | Описание | | --- | --- | --- | --- | | event | string | событие | Тип: pageview, click, submit, … | | page_type | string | null | getPageType | Тип страницы | | page_direction | string | null | getPageDirection | Направление (apartments/offices/…) | | page_project | string | null | getProjectName | Slug проекта | | page_lot | string | null | getLotName | Slug лота | | ym_uid | string | null | getYmUid(95057596) | Client ID Яндекс.Метрики | | cm_visitor_id | string | null | Comagic.getVisitorId() | ID посетителя Comagic | | fingerprint_id | string | null | FingerprintJS | Браузерный fingerprint | | visible_virtual_pn | string | undefined | MD5 #header_phone | Хеш виртуального номера | | ab_test_name | string | store | Всегда 'abgroup' | | ab_test_group | string | null | NEXT_PUBLIC_AB_TEST_GROUP | Группа A/B-теста | | user_id | string | null | user_reg_id | ID пользователя | | auth | boolean | auth_store | Авторизован ли пользователь | | widget | object | undefined | вызывающий код | { widget_agent_id, widget_session_id, response_time_ms } |

API

POST /api/analytics/send-event

Внешний эндпоинт бэкенда STONE. Вызывается из sendAnalyticsToAPI.

POST https://stone.ru/api/analytics/send-event
Content-Type: application/json

Тело запроса:

{
  "event": "click",
  "name": "banner_main",
  "payload": {
    "st_client_id": "1710000000123456789",
    "st_session_id": "1710000000987654321",
    "st_client_hash_id": "abc123...",
    "ym_client_id": "...",
    "ga_client_id": "...",
    "project": "sokolniki",
    "page_path": "/catalog/residential/sokolniki/lot-1",
    "page_url": "https://stone.ru/catalog/residential/sokolniki/lot-1"
  }
}

| Поле | Тип | Описание | | --- | --- | --- | | event | string | Тип события | | name | string | Имя/идентификатор события | | payload | object | Параметры + автоматически добавленные page_path, page_url |

Ответ: стандартный ответ бэкенда (пакет не обрабатывает тело ответа).

Поведение:

  • Запрос не отправляется, если NEXT_PUBLIC_IS_TEST === 'true'
  • Запрос не отправляется на сервере (SSR) — только в браузере
  • Ошибки сети логируются в консоль, не пробрасываются

Контракт dataLayer

Основной транспорт событий — window.dataLayer. Каждый метод stoneDataLayerStore.setEvent* формирует объект и вызывает pushLayer.

Общий шаблон:

{
  "event": "<тип>",
  "page_type": "lot_card",
  "page_direction": "apartments",
  "page_project": "sokolniki",
  "page_lot": "lot-1",
  "ym_uid": "123456789",
  "cm_visitor_id": "cm-abc",
  "fingerprint_id": "fp-xyz",
  "visible_virtual_pn": "a1b2c3...",
  "ab_test_name": "abgroup",
  "ab_test_group": "A",
  "user_id": null,
  "auth": false
}

Типы событий

pageview

stoneDataLayerStore.setEventPageView({ auth?, widget? })

Просмотр страницы. Не включает ym_uid в payload (удаляется перед push).


pageview_force

stoneDataLayerStore.setEventPageViewForce()

Тестовая отправка pageview без ожидания загрузки внешних ID.


click

stoneDataLayerStore.setEventClick({
  action_element?, action_element_text?, block_name?,
  clicked_url?, clicked_lot?, clicked_lot_project?,
  clicked_lot_position?, action_element_status?,
  banner_position?, banner_text?, auth?, widget?
})

Клик по элементу. Текст очищается через cleanString (удаление &nbsp;, <br>, лишних пробелов).


submit

stoneDataLayerStore.setEventSubmit({
  lead_id, block_name?, submitted_lot?,
  submitted_lot_project?, auth?, widget?
})

Отправка формы / создание лида.


lot_feed_show

stoneDataLayerStore.setEventLotFeedShow({
  block_name?, list_of_lots: [{ position, lot, lot_project }],
  widget?
})

Показ ленты лотов. Не включает ym_uid и cm_visitor_id.


check

stoneDataLayerStore.setEventCheck({
  action_element, action_element_text, block_name?, auth?, widget?
})

Чекбокс / переключатель.


blur

stoneDataLayerStore.setEventBlur({
  action_element, action_element_status: 'error' | 'success',
  block_name?, auth?, widget?
})

Потеря фокуса поля формы (валидация).


banner_show / banner_click

stoneDataLayerStore.setEventBannerShow({ banner_text, banner_link, banner_position?, block_name?, auth?, widget? })
stoneDataLayerStore.setEventBannerClick({ banner_text, banner_link, clicked_url?, block_name?, auth?, widget? })

Показ и клик по баннеру.


onboarding_show / onboarding_close

stoneDataLayerStore.setEventOnboardingShow({ banner_text, banner_link, banner_position?, block_name?, auth?, widget? })
stoneDataLayerStore.setEventOnboardingClose({ banner_text, banner_link, banner_position?, block_name?, auth?, widget? })

Показ и закрытие онбординга.


chat

stoneDataLayerStore.setEventChat({
  action_element, action_element_text, message?,
  block_name?, auth?, widget?
})

Действия в чате.


fullscreenview

stoneDataLayerStore.setEventFullScreen({ auth?, widget? })

Просмотр в полноэкранном режиме.


Сводная таблица событий

| Метод store | event | YM/Comagic ID | visible_virtual_pn | Специфичные поля | | --- | --- | --- | --- | --- | | setEventPageView | pageview | — | — | auth, widget | | setEventPageViewForce | pageview_force | — | — | auth, widget | | setEventClick | click | + | + | action_element, clicked_url, clicked_lot, banner_* | | setEventSubmit | submit | + | + | lead_id, submitted_lot, submitted_lot_project | | setEventLotFeedShow | lot_feed_show | — | + | list_of_lots, block_name | | setEventCheck | check | + | + | action_element, action_element_text | | setEventBlur | blur | + | + | action_element, action_element_status | | setEventBannerShow | banner_show | + | — | banner_text, banner_link, banner_position | | setEventBannerClick | banner_click | + | — | banner_text, banner_link, clicked_url | | setEventOnboardingShow | onboarding_show | + | — | banner_text, banner_link, banner_position | | setEventOnboardingClose | onboarding_close | + | — | banner_text, banner_link, banner_position | | setEventChat | chat | + | — | action_element, action_element_text, message | | setEventFullScreen | fullscreenview | + | + | auth, widget |

Сценарии использования

Подключение в Next.js-приложении

sequenceDiagram
    participant Dev as Разработчик
    participant App as AppProvider
    participant Hook as useInitializeMetrics
    participant LS as localStorage
    participant Page as Страница
    participant Store as stoneDataLayerStore
    participant DL as dataLayer

    Dev->>App: npm i stone-analytics
    Dev->>App: useInitializeMetrics() в корневом провайдере
    Hook->>LS: setClientId() / setSessionId()
    Page->>Store: setPageParams({ pathname, source, href })
    Page->>Store: setEventPageView()
    Store->>DL: pushLayer(event)
import { useInitializeMetrics, stoneDataLayerStore } from 'stone-analytics'

export const AppProvider = ({ children }: { children: React.ReactNode }) => {
  useInitializeMetrics()
  return <>{children}</>
}

// На странице:
stoneDataLayerStore.setPageParams({
  pathname: '/catalog/residential/sokolniki/lot-1',
  source: 'stone',
  href: window.location.href,
})
await stoneDataLayerStore.setEventPageView({ auth: false })

Пайплайн отправки события

flowchart TD
    Start([Вызов setEventClick]) --> PageParams{"pageParamsWasSet?"}
    PageParams -->|нет| Warn["page_type/direction/project/lot = undefined"]
    PageParams -->|да| Default["getDefaultAnalyticsData(store)"]
    Warn --> Default
    Default --> Enrich["setAnalyticId: ym_uid + cm_visitor_id"]
    Enrich --> VirtualPn["visible_virtual_pn = getVisibleVirtualPn()"]
    VirtualPn --> IsTest{"NEXT_PUBLIC_IS_TEST === 'true'?"}
    IsTest -->|да| Skip["Событие не пушится"]
    IsTest -->|нет| Push["pushLayer → window.dataLayer"]
    Push --> GTM["GTM-триггеры обрабатывают событие"]

Кейс: просмотр страницы

  1. Пользователь открывает /catalog/residential/sokolniki/lot-42
  2. Приложение вызывает setPageParams({ pathname, source: 'stone', href })
  3. getPageTypelot_card, getPageDirectionapartments, getProjectNamesokolniki, getLotNamelot-42
  4. Приложение вызывает setEventPageView({ auth: false })
  5. В dataLayer пушится объект с event: 'pageview' и контекстом страницы
  6. GTM отправляет данные в подключённые системы аналитики

Кейс: клик по лоту в каталоге

  1. Пользователь кликает на карточку лота в блоке catalog_grid
  2. Компонент вызывает:
await stoneDataLayerStore.setEventClick({
  block_name: 'catalog_grid',
  action_element: 'lot_card',
  action_element_text: 'Квартира 45 м²',
  clicked_lot: 'lot-42',
  clicked_lot_project: 'sokolniki',
  clicked_lot_position: '3',
  auth: false,
})
  1. Текст очищается от HTML-сущностей
  2. Добавляются ym_uid, cm_visitor_id, visible_virtual_pn
  3. Событие click попадает в dataLayer

Кейс: отправка формы (submit)

  1. Пользователь отправляет форму обратной связи
  2. Бэкенд возвращает lead_id
  3. Компонент вызывает:
await stoneDataLayerStore.setEventSubmit({
  lead_id: '12345',
  block_name: 'contact_form',
  submitted_lot: 'lot-42',
  submitted_lot_project: 'sokolniki',
  auth: true,
})
  1. В dataLayer пушится event: 'submit' с lead_id

Кейс: истечение сессии

Закрытие последней вкладки:

  1. beforeunload декрементирует tabCount
  2. Если tabCount === 0st_session_id очищается
  3. При следующем визите useInitializeMetrics создаёт новый st_session_id

Неактивность 30 минут:

  1. Таймер сбрасывается при mousemove, click, touchstart, scroll, keydown
  2. По истечении 30 мин → st_session_id очищается
  3. Следующее событие получит новый session ID при перезагрузке

Кейс: тестовый режим (события не отправляются)

  1. В .env.local приложения-потребителя: NEXT_PUBLIC_IS_TEST=true
  2. pushLayer, dataLayerPush, sendAnalyticsToAPI, ymTarget — все проверяют этот флаг
  3. События формируются, но не отправляются ни в dataLayer, ни на API, ни в Яндекс.Метрику
  4. Удобно для локальной разработки без засорения аналитики

Установка

В проекте-потребителе (Next.js / React)

npm i stone-analytics@latest

Требуется React 18+ (peer-зависимость, не устанавливается автоматически).

Локальная разработка пакета

git clone <repo-url> stone-analytics
cd stone-analytics
npm install

Запуск

Разработка пакета

# Сборка с watch-режимом (tsup)
npx tsup --watch

# Одноразовая сборка
npm run build

Использование в приложении-потребителе

# В .env.local приложения
NEXT_PUBLIC_IS_TEST=true          # отключить отправку событий
NEXT_PUBLIC_AB_TEST_GROUP=A       # группа A/B-теста
NEXT_PUBLIC_YM_COUNTER=95057596   # счётчик Яндекс.Метрики (для ymTarget)
// app/providers.tsx
import { useInitializeMetrics } from 'stone-analytics'

export function Providers({ children }) {
  useInitializeMetrics()
  return children
}

Команды

| Команда | Описание | | --- | --- | | npm run build | Сборка в dist/ (CJS + .d.ts, minify, sourcemap) | | npm run version-update | Инкремент patch-версии в package.json | | npm run commit-and-push | Коммит и push с сообщением Update version to X.Y.Z | | npm run release | Полный цикл: build → version-update → commit-and-push → npm publish --tag latest | | npm version (lifecycle) | Автоматически запускает build и git add . |

Развертывание

Зависимости потребителя

Для полной работы аналитики на сайте-потребителе необходимы:

| Зависимость | Назначение | | --- | --- | | React 18+ | Хуки useInitializeMetrics, useComagicStatus | | Google Tag Manager | Скрипт GTM на странице, читает window.dataLayer | | Яндекс.Метрика | Скрипт ym, счётчик 95057596 (hardcoded в getYmUid) | | Comagic | Скрипт Comagic для getVisitorId() | | Элемент #header_phone | <a id="header_phone" href="tel:..."> для visible_virtual_pn |

Публикация в npm

# Полный релиз (build + bump version + commit + push + publish)
npm run release

# Или по шагам:
npm run build
npm run version-update
npm publish --tag latest

Пакет публикуется как stone-analytics с "access": "public". Точки входа:

| Формат | Путь | | --- | --- | | CJS | dist/index.cjs | | ESM | dist/index.js | | Types | dist/index.d.ts |

prepublishOnly автоматически запускает build перед публикацией.

Переменные окружения

Переменные задаются в приложении-потребителе (Next.js), не в самом пакете:

| Переменная | Описание | Значение по умолчанию | | --- | --- | --- | | NEXT_PUBLIC_IS_TEST | Тестовый режим: отключает отправку во все транспорты | — (отправка включена) | | NEXT_PUBLIC_AB_TEST_GROUP | Группа A/B-теста, попадает в ab_test_group | — | | NEXT_PUBLIC_YM_COUNTER | ID счётчика Яндекс.Метрики для ymTarget | — |

Интеграция с внешними системами

| Система | Механизм | ID счётчика / конфиг | | --- | --- | --- | | Google Tag Manager | window.dataLayer.push() | Настраивается на стороне GTM | | Яндекс.Метрика | getYmUid(95057596) в событиях; ymTargetreachGoal | 95057596 (hardcoded) | | Comagic | Comagic.getVisitorId() | Скрипт Comagic на странице | | Google Analytics | getGoogleAnalyticsClientId() из cookie _ga | Cookie GA | | FingerprintJS | getStoneHashClientId()fingerprint_id | Open-source FingerprintJS | | Бэкенд STONE | POST stone.ru/api/analytics/send-event | Без авторизации на стороне клиента |

Источники страниц (TSource): stone, sokolniki, stone-hod, stonehod, realty — влияют на маппинг page_type в pageType.ts.

Чек-лист

Перед публикации новой версии проверьте:

  • [ ] npm run build проходит без ошибок, dist/ актуален
  • [ ] Типы в dist/index.d.ts соответствуют экспортам из src/index.ts
  • [ ] Версия в package.json инкрементирована
  • [ ] NEXT_PUBLIC_IS_TEST=true работает — события не уходят в dataLayer/API/YM
  • [ ] useInitializeMetrics корректно создаёт/обновляет st_client_id и st_session_id
  • [ ] setPageParams вызывается до setEvent* на каждой странице
  • [ ] Все 13 типов событий (setEvent*) формируют корректный payload
  • [ ] README актуален: версия пакета, список экспортов, переменные окружения
  • [ ] Примеры кода в README компилируются и соответствуют API
  • [ ] npm publish выполнен с тегом latest