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 має:

Використання

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';

Ліцензія

MIT.