Модульная система позволяет расширять функциональность Segmenta без изменения ядра. Каждый модуль — это отдельная папка в modules/ с серверной и клиентской частью.

Принцип работы

При запуске проекта (pnpm dev) система:
  1. Запускает скрипт pnpm modules:generate — сканирует папку modules/ и генерирует файлы импортов
  2. Собирает из каждого модуля: настройки, роутеры, схемы БД и хуки
  3. Регистрирует всё в едином реестре (moduleRegistry)
  4. Модули можно включать/выключать через админ-панель без перезапуска

Структура модуля

modules/my-module/
├── index.ts             # Точка входа — defineModule()
├── package.json         # npm-пакет модуля
├── schema.ts            # Схемы таблиц БД (Drizzle)
├── router.ts            # tRPC-роутер модуля
├── service.ts           # Бизнес-логика
└── client/              # Клиентская часть
    ├── package.json
    ├── tsconfig.json
    ├── index.ts
    ├── register.ts      # Регистрация компонентов в слотах
    └── components/
        └── MyWidget.tsx

Создание модуля

1

Создайте папку модуля

mkdir modules/daily-rewards
2

Создайте package.json

modules/daily-rewards/package.json
{
  "name": "@segmenta/daily-rewards",
  "version": "1.0.0",
  "private": true,
  "main": "./index.ts",
  "types": "./index.ts",
  "dependencies": {
    "@segmenta/modules": "workspace:*"
  }
}
3

Создайте index.ts

modules/daily-rewards/index.ts
import { defineModule } from '../core';

export default defineModule({
  id: 'daily-rewards',
  name: 'Ежедневные награды',
  version: '1.0.0',
  description: 'Выдаёт бонусы за ежедневный вход',
  icon: 'Calendar',

  settings: {
    fields: {
      enabled: {
        type: 'boolean',
        label: 'Включить награды',
        default: true,
      },
      rewardAmount: {
        type: 'number',
        label: 'Количество монет',
        default: 100,
        validation: { min: 1, max: 10000 },
      },
    },
  },

  server: {
    hooks: {
      onUserLogin: async (userId) => {
        console.log(`Проверяем награду для ${userId}`);
      },
    },
  },
});
4

Сгенерируйте импорты и запустите

Регистрировать модуль вручную не нужно — скрипт pnpm modules:generate сканирует папку modules/ и автоматически собирает _register.generated.ts (импорты и вызовы moduleRegistry.register()), _schema.generated.ts и _client-register.generated.ts. Файл modules/register.ts лишь реэкспортирует сгенерированный реестр.
pnpm modules:generate
pnpm dev
Модуль появится в админ-панели в разделе «Модули».

Серверная часть

Схема БД

Создайте файл schema.ts и подключите его в index.ts:
modules/daily-rewards/schema.ts
import { mysqlTable, serial, int, timestamp } from 'drizzle-orm/mysql-core';

export const dailyRewardsHistory = mysqlTable('daily_rewards_history', {
  id: serial('id').primaryKey(),
  userId: int('user_id').notNull(),
  claimedAt: timestamp('claimed_at').defaultNow(),
});
modules/daily-rewards/index.ts
import * as schema from './schema';

export default defineModule({
  // ...
  server: {
    schema,
    // ...
  },
});
После добавления схемы выполните pnpm db:push для создания таблиц.

tRPC-роутер

Создайте router.ts — API-методы модуля:
modules/daily-rewards/router.ts
export const dailyRewardsRouter = (router, protectedProcedure, publicProcedure, adminProcedure) =>
  router({
    claim: protectedProcedure.mutation(async ({ ctx }) => {
      // Логика выдачи награды
      return { success: true, amount: 100 };
    }),

    history: protectedProcedure.query(async ({ ctx }) => {
      // Получить историю наград
      return [];
    }),
  });
Подключите в index.ts:
import { dailyRewardsRouter } from './router';

export default defineModule({
  // ...
  server: {
    router: dailyRewardsRouter,
    routerKey: 'dailyRewards',
    // ...
  },
});
После этого роутер будет доступен на клиенте как trpc.modules.dailyRewards.claim.mutate().

Хуки

Модули могут реагировать на системные события:
ХукКогда срабатывает
onUserCreateРегистрация нового пользователя
onUserLoginВход пользователя
onUserUpdateОбновление профиля
onPaymentCompleteУспешный платёж
onBalanceTopUpПополнение баланса
onServerStatusChangeИзменение статуса сервера
onPrivilegePurchaseПокупка привилегии
onBanCreateБан пользователя
onReferralActivatedАктивация реферала
onPlaytimeUpdateОбновление времени игры
onReferralRewardPaidВыплата реферальной награды
server: {
  hooks: {
    onPaymentComplete: async (paymentId, userId, amount) => {
      if (amount > 1000) {
        // Выдать бонус за крупный донат
      }
    },
  },
}

Клиентская часть

Структура

modules/daily-rewards/client/
├── package.json
├── index.ts
├── register.ts
└── components/
    └── RewardCard.tsx

package.json

modules/daily-rewards/client/package.json
{
  "name": "@segmenta/daily-rewards-client",
  "version": "1.0.0",
  "private": true,
  "main": "./index.ts",
  "types": "./index.ts",
  "exports": {
    ".": { "import": "./index.ts", "types": "./index.ts" }
  },
  "dependencies": {
    "@segmenta/modules": "workspace:*"
  },
  "peerDependencies": {
    "react": "^19.0.0"
  }
}

register.ts — регистрация в слотах

modules/daily-rewards/client/register.ts
import { clientModuleRegistry, type ClientExtensionRegistry } from '@segmenta/modules';
import { RewardCard } from './components/RewardCard';

function registerExtensions(registry: ClientExtensionRegistry) {
  registry.registerBatch('daily-rewards', 'profile.overview.cards', [
    {
      id: 'daily-reward-card',
      order: 10,
      component: RewardCard,
    },
  ]);
}

clientModuleRegistry.addModule(registerExtensions);

index.ts

modules/daily-rewards/client/index.ts
import './register';
export * from './components/RewardCard';

Слоты (точки расширения)

Слоты — это места в интерфейсе, куда модули могут вставлять свои компоненты.

Профиль пользователя

СлотОписание
profile.tabsНовая вкладка в профиле
profile.overview.cardsКарточки на вкладке «Обзор»
profile.overview.sectionsСекции на вкладке «Обзор»
profile.header.actionsКнопки в шапке профиля

Админ-панель

СлотОписание
admin.dashboard.widgetsВиджеты на дашборде
admin.dashboard.cardsКарточки на дашборде
admin.sidebar.itemsПункты меню в сайдбаре
admin.module.pagesСтраница модуля в админ-панели

Лейаут

СлотОписание
header.leftЭлементы слева в шапке сайта
header.rightЭлементы справа в шапке сайта
header.navСсылки в шапке сайта
footer.sectionsСекции в подвале
footer.linksСсылки в подвале
footer.bottomНижняя строка подвала

Прочее

СлотОписание
pagesПубличные страницы модуля (зарегистрированный path монтируется под /m)
server.card.footerПодвал карточки сервера
news.card.footerПодвал карточки новости
auth.register.fieldsПоля на форме регистрации
auth.login.fieldsПоля на форме входа

Настройки модуля

Настройки описываются в поле settings и автоматически отображаются в админ-панели.

Типы полей

ТипОписание
stringТекстовое поле
numberЧисловое поле
booleanПереключатель
selectВыпадающий список
multiselectМножественный выбор
colorВыбор цвета
urlПоле для URL
jsonJSON-редактор
rewardНастройка награды (фикс/процент)
durationНастройка длительности

Условная видимость

Поля можно показывать/скрывать в зависимости от значений других полей:
settings: {
  fields: {
    enableBonus: {
      type: 'boolean',
      label: 'Включить бонус',
      default: false,
    },
    bonusAmount: {
      type: 'number',
      label: 'Размер бонуса',
      default: 50,
      showIf: { field: 'enableBonus', operator: 'eq', value: true },
    },
  },
}

Чтение настроек в коде

const moduleData = moduleRegistry.get('daily-rewards');
const rewardAmount = moduleData?.settings?.rewardAmount ?? 100;

Эталонный модуль

Для примера смотрите модуль referrals (modules/referrals/). В нём реализовано всё: схема БД, tRPC-роутер, клиентские компоненты, настройки и хуки.