Common
@hopak/common — спільні класи помилок, логер, HTTP-статуси. Використовується кожним іншим @hopak/* пакетом.
Спільні примітиви для Hopak.js — ієрархія помилок, логер, HTTP-статуси, файлові хелпери, утиліти та типи конфігу. Використовуються кожним іншим @hopak/* пакетом.
Напряму встановлювати зазвичай не потрібно.
@hopak/coreвже залежить від@hopak/commonі реекспортує весь його публічний API. Встановлюйте цей пакет, лише коли пишете власний Hopak-плагін чи розширення.
Встановлення
bun add @hopak/common
Усе, що експортується тут, також доступно з @hopak/core:
// equivalent imports
import { HopakError, NotFound, HttpStatus, createLogger } from '@hopak/core';
import { HopakError, NotFound, HttpStatus, createLogger } from '@hopak/common';
Помилки
Усі помилки фреймворку розширюють HopakError. Кинута всередині route-handler, будь-яка з них дає структуровану JSON-відповідь із правильним статусом.
Ієрархія
import {
HopakError, // base — status 500
ValidationError, // 400
Unauthorized, // 401
Forbidden, // 403
NotFound, // 404
Conflict, // 409
RateLimited, // 429
InternalError, // 500
ConfigError, // 500 — config-layer problems
} from '@hopak/common';
Структура
Кожна HopakError має:
status: number— HTTP-статусcode: string— машинозчитуваний код помилки (наприклад,"NOT_FOUND")message: string— людинозчитуване повідомленняdetails?: unknown— опціональне навантаження (використовуєтьсяValidationErrorдля помилок полів)toJSON()— тіло відповіді
Використання
throw new NotFound('Post not found');
// → 404 { "error": "NOT_FOUND", "message": "Post not found" }
throw new ValidationError('Invalid body', { email: ['Required'] });
// → 400 { "error": "VALIDATION_ERROR", "message": "Invalid body",
// "details": { "email": ["Required"] } }
Детальніше — Помилки.
Логер
Інтерфейс
export interface Logger {
debug(message: string, meta?: LogMeta): void;
info(message: string, meta?: LogMeta): void;
warn(message: string, meta?: LogMeta): void;
error(message: string, meta?: LogMeta): void;
child(bindings: LogMeta): Logger;
}
LogMeta — це Record<string, unknown> | object: будь-який звичайний обʼєкт підходить.
ConsoleLogger
Вбудована реалізація пише JSON-тегові рядки у stdout (або stderr для помилок). Колір і мітка часу включені.
import { createLogger } from '@hopak/common';
const log = createLogger({ level: 'debug' });
log.info('server up', { port: 3000 });
log.error('DB connection failed', { cause: 'ECONNREFUSED' });
Дочірні логери
Попередньо привʼязуйте контекст до наявного логера:
const reqLog = log.child({ requestId: '123' });
reqLog.info('handler called');
// → [2026-...] INFO handler called {"requestId":"123"}
Підключення власного логера
Logger — це звичайний інтерфейс; підходить усе, що йому відповідає. Перенаправляйте в pino, winston або у власний транспорт.
HTTP-статуси
import { HttpStatus } from '@hopak/common';
HttpStatus.Ok; // 200
HttpStatus.Created; // 201
HttpStatus.NoContent; // 204
HttpStatus.BadRequest; // 400
HttpStatus.Unauthorized; // 401
HttpStatus.Forbidden; // 403
HttpStatus.NotFound; // 404
HttpStatus.MethodNotAllowed; // 405
HttpStatus.Conflict; // 409
HttpStatus.TooManyRequests; // 429
HttpStatus.InternalServerError; // 500
Експортується як const-обʼєкт і відповідний тип-обʼєднання — використовуйте обʼєкт для значень, тип — для анотацій:
import { HttpStatus } from '@hopak/common';
function respond(status: HttpStatus) { /* ... */ }
respond(HttpStatus.Created);
Файлові хелпери
Асинхронні обгортки навколо node:fs/promises, які повертають false замість викидання помилки, коли шлях відсутній.
import { pathExists, isFile, isDirectory } from '@hopak/common';
await pathExists('./hopak.config.ts'); // true | false
await isFile('./README.md'); // true only if a regular file
await isDirectory('./app/models'); // true only if a directory
Утиліти
slugify(input)
slugify('Hello, World!') // 'hello-world'
slugify('Привіт світ') // '' (ASCII-only; non-latin input yields empty)
Переводить у нижній регістр, обрізає, прибирає не-word символи, згортає пробіли до -.
pluralize(word)
Простий англійський плюралізатор, який CRUD-скафолдер використовує для URL-сегментів:
pluralize('post') // 'posts'
pluralize('story') // 'stories'
pluralize('box') // 'boxes'
parseDuration(input)
Парсить "100ms", "5s", "10m", "1h", "7d" у мілісекунди:
parseDuration('5s') // 5000
parseDuration('2h') // 7200000
Кидає на невідомих одиницях або неправильному форматі.
deepMerge(target, source)
Рекурсивно зливає звичайні обʼєкти. Масиви та примітиви в source замінюють ті, що в target. undefined у source ігнорується. Використовується в шарі конфігу, щоб накладати користувацькі перевизначення поверх значень за замовчуванням.
deepMerge({ a: { x: 1, y: 2 } }, { a: { y: 20, z: 30 } });
// { a: { x: 1, y: 20, z: 30 } }
Типи конфігу
import type {
HopakConfig,
HopakConfigInput,
HopakPaths,
ServerOptions,
HttpsOptions,
DatabaseOptions,
CorsOptions,
DbDialect,
RuntimeContext,
DeepPartial,
} from '@hopak/common';
HopakConfig— повністю розвʼязаний обʼєкт конфігу (те, що фреймворк бачить у рантаймі)HopakConfigInput—DeepPartial<HopakConfig>; форма, яку ви передаєте вdefineConfig({...})HopakPaths— розвʼязані директоріїmodels/routes/jobs/public/migrations/hopakDirServerOptions—{ port, host, https? }HttpsOptions—{ enabled, port?, cert?, key? }DatabaseOptions—{ dialect, url?, file? }CorsOptions—{ origins, credentials? }DbDialect—'sqlite' | 'postgres' | 'mysql'RuntimeContext—{ log, config }; корисно при написанні плагінівDeepPartial<T>— рекурсивнийPartial
Ліцензія
MIT.