@qor/chat-layer
Установка
Подключение @qor/chat-layer к Nuxt-приложению.
Требования
- Node.js >= 20
- pnpm >= 9
- Nuxt >= 4.0
@qor/auth-layer(подключается автоматически как зависимость)@qor/ui+@nuxt/ui(для UI-компонентов)
Установка пакета
# Если chat-layer в монорепо
pnpm add @qor/chat-layer
pnpm add @qor/chat-layer
Подключение в Nuxt
1. Расширить layer
nuxt.config.ts
export default defineNuxtConfig({
extends: ['@qor/chat-layer'],
modules: ['@qor/ui', '@nuxt/ui'],
// Chat layer — SPA, SSR отключен
ssr: false,
})
@qor/uiMUST быть указан до@nuxt/ui в массиве modules. Иначе CSS-переменные для семантических цветов не будут сгенерированы.2. Настроить runtime config
nuxt.config.ts
export default defineNuxtConfig({
extends: ['@qor/chat-layer'],
runtimeConfig: {
// === Server-only ===
// Prisma database URL (SQLite по умолчанию)
chatDatabaseUrl: 'file:./chat.db',
// Директория для загруженных файлов
chatUploadDir: './uploads',
// === Public ===
public: {
// Base URL для REST API чата
// Пусто = локальные Nitro-эндпоинты
// URL = внешний API (серверные роуты исключаются)
chatApiBase: '',
// WebSocket URL для Socket.IO
// Пусто = auto-detect от текущего origin
chatWsUrl: '',
// Auth layer config (обязательно)
authApiBase: '',
},
},
})
3. Переменные окружения
.env
# Prisma
CHAT_DATABASE_URL="file:./chat.db"
# Загрузки
NUXT_CHAT_UPLOAD_DIR="./uploads"
# Режим: локальный сервер (по умолчанию)
NUXT_PUBLIC_CHAT_API_BASE=""
NUXT_PUBLIC_CHAT_WS_URL=""
# Или: внешний API
# NUXT_PUBLIC_CHAT_API_BASE="https://chat.api.example.com"
# NUXT_PUBLIC_CHAT_WS_URL="wss://chat.api.example.com"
# Auth layer
NUXT_PUBLIC_AUTH_API_BASE=""
4. CSS (обязательно)
Убедитесь, что в вашем приложении есть main.css:
app/assets/css/main.css
@import "tailwindcss";
@import "@nuxt/ui";
nuxt.config.ts
export default defineNuxtConfig({
css: ['~/assets/css/main.css'],
})
5. Инициализация Prisma
При первом запуске сгенерируйте Prisma-клиент и создайте базу данных:
# Генерация клиента
npx prisma generate --schema=node_modules/@qor/chat-layer/prisma/schema.prisma
# Создание/миграция базы
npx prisma db push --schema=node_modules/@qor/chat-layer/prisma/schema.prisma
Prisma-клиент генерируется в
node_modules/.prisma/chat-client — это кастомный output, чтобы не конфликтовать с другими Prisma-схемами в монорепо.Конфигурация
Полная таблица параметров
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
chatDatabaseUrl | string | 'file:./chat.db' | Prisma database URL (server-only) |
chatUploadDir | string | './uploads' | Директория для загруженных файлов (server-only) |
chatApiBase | string | '' | Base URL для REST API. Пусто = локальный |
chatWsUrl | string | '' | WebSocket URL. Пусто = auto-detect |
Режимы работы
Локальный сервер (разработка, standalone)
nuxt.config.ts
export default defineNuxtConfig({
extends: ['@qor/chat-layer'],
runtimeConfig: {
chatDatabaseUrl: 'file:./chat.db',
chatUploadDir: './uploads',
public: {
chatApiBase: '',
chatWsUrl: '',
},
},
})
В этом режиме chat layer поднимает:
- Socket.IO сервер через Engine.IO (привязан к Nitro HTTP серверу)
- REST API
/api/v1/chat/*для истории, загрузок, управления - Маршрут
/uploads/*для раздачи загруженных файлов - SQLite база данных для хранения сообщений
Внешний API (продакшн)
nuxt.config.ts
export default defineNuxtConfig({
extends: ['@qor/chat-layer'],
runtimeConfig: {
public: {
chatApiBase: 'https://chat.api.example.com',
chatWsUrl: 'wss://chat.api.example.com',
},
},
})
В этом режиме:
- Серверные роуты
/api/v1/chat/*исключаются из бандла черезnitro:configхук - Socket.IO клиент подключается к
chatWsUrl - REST-запросы проксируются на
chatApiBase - Prisma и SQLite не используются
REST API
Эндпоинты (локальный режим)
| Метод | Путь | Описание |
|---|---|---|
GET | /api/v1/chat/conversations | Список диалогов текущего пользователя |
POST | /api/v1/chat/conversations | Создать диалог (single/group) |
GET | /api/v1/chat/conversations/:id/messages | История сообщений (cursor-based) |
POST | /api/v1/chat/upload | Загрузить файл (multipart/form-data) |
POST | /api/v1/chat/users/webhook | Webhook для создания/обновления ChatUser |
GET | /api/v1/chat/users/:id | Публичный профиль пользователя |
GET | /uploads/:path | Раздача загруженных файлов |
Создание диалога
// POST /api/v1/chat/conversations
const conv = await $fetch('/api/v1/chat/conversations', {
method: 'POST',
body: {
type: 'single', // 'single' | 'group'
participantIds: ['user-uuid'], // externalId участников
name: 'Группа поддержки', // Только для group
},
})
Загрузка файла
// POST /api/v1/chat/upload
const formData = new FormData()
formData.append('file', file)
const attachment = await $fetch('/api/v1/chat/upload', {
method: 'POST',
body: formData,
})
// → { id, type, url, name, size, mimeType }
Загруженный файл создаётся как "orphan" (messageId = null). При отправке сообщения через Socket.IO передаётся attachmentIds, и сервер привязывает вложения к сообщению.
Пагинация сообщений
// GET /api/v1/chat/conversations/:id/messages?cursor=2025-01-01T12:00:00.000Z
const result = await $fetch(`/api/v1/chat/conversations/${convId}/messages`)
// → { messages: ChatMessage[], cursor: string, hasMore: boolean }
Cursor-based пагинация по createdAt. Курсор — ISO timestamp последнего сообщения.
Socket.IO события
Клиент → Сервер
| Событие | Payload | Описание |
|---|---|---|
message:send | { conversationId, body, type?, replyToId?, attachmentIds? } | Отправить сообщение (с ack) |
message:edit | { messageId, body } | Редактировать сообщение |
message:delete | { messageId } | Удалить сообщение |
message:react | { messageId, emoji } | Добавить/убрать реакцию |
message:read | { conversationId, messageId } | Отметить прочитанным |
typing:start | conversationId | Начал печатать |
typing:stop | conversationId | Перестал печатать |
conversation:join | conversationId | Войти в комнату |
conversation:leave | conversationId | Покинуть группу |
Сервер → Клиент
| Событие | Payload | Описание |
|---|---|---|
message:new | ChatMessage | Новое входящее сообщение |
message:updated | { id, conversationId, body?, editedAt? } | Сообщение отредактировано |
message:deleted | { id, conversationId } | Сообщение удалено |
message:reaction | { messageId, conversationId, reactions[] } | Обновление реакций |
message:status | { messageId, conversationId, status } | Статус доставки изменился |
conversation:created | ChatConversation | Создан новый диалог |
conversation:updated | Partial<ChatConversation> & { id } | Обновление диалога |
presence:update | { userId, online, lastSeen? } | Статус присутствия |
typing:start | { conversationId, userId } | Пользователь печатает |
typing:stop | { conversationId, userId } | Пользователь перестал |
Регистрация пользователей
Chat layer использует свою таблицу ChatUser с маппингом на внешний externalId. Пользователи создаются автоматически:
- При создании диалога —
POST /api/v1/chat/conversationsавтоматически вызывает upsert для каждогоparticipantId - Через webhook —
POST /api/v1/chat/users/webhookдля массового создания из внешней системы
// Webhook: создать/обновить ChatUser
await $fetch('/api/v1/chat/users/webhook', {
method: 'POST',
body: {
externalId: 'user-uuid-from-auth',
displayName: 'Иван Петров',
avatarUrl: 'https://example.com/avatar.jpg',
},
})
Быстрый старт
Минимальная конфигурация для запуска:
nuxt.config.ts
export default defineNuxtConfig({
extends: ['@qor/chat-layer'],
modules: ['@qor/ui', '@nuxt/ui'],
ssr: false,
css: ['~/assets/css/main.css'],
})
Terminal
# 1. Установить зависимости
pnpm install
# 2. Сгенерировать Prisma-клиент
npx prisma generate --schema=node_modules/@qor/chat-layer/prisma/schema.prisma
# 3. Создать базу данных
npx prisma db push --schema=node_modules/@qor/chat-layer/prisma/schema.prisma
# 4. Запустить dev-сервер
pnpm dev
После запуска:
- Авторизуйтесь через auth layer
- Перейдите на
/chats - Chat layer автоматически подключит Socket.IO при появлении
useAuth().isAuthenticated