TypeScript позволяет расширять существующие типы библиотеки через declaration merging, module augmentation и utility types. Это открывает возможность адаптировать типизацию под конкретные нужды проекта без форка библиотеки.
// types/timeago.d.ts
declare module 'timeago.js' {
// Добавить строгий тип локали
export type SupportedLocale =
| 'ru' | 'en_US' | 'de' | 'fr' | 'es'
| 'zh_CN' | 'zh_TW' | 'ja' | 'ko' | 'ar'
| 'pt_BR' | 'it' | 'nl' | 'pl' | 'tr';
// Перегрузка format с ограниченным набором локалей
export function format(
date: Date | string | number,
locale?: SupportedLocale,
opts?: FormatOptions
): string;
}
После добавления этого файла TypeScript будет проверять локаль на этапе компиляции.
declare module 'timeago.js' {
interface FormatOptions {
relativeDate?: Date | number;
// Добавить собственные поля
fallback?: string;
maxAge?: number;
}
}
Поля fallback и maxAge — кастомные: сама
библиотека их не читает, но их можно использовать в обёртке:
import { format } from 'timeago.js';
import type { FormatOptions } from 'timeago.js';
function safeFormat(date: Date | string | number, locale = 'ru', opts?: FormatOptions): string {
if (opts?.maxAge) {
const age = Date.now() - new Date(date).getTime();
if (age > opts.maxAge) {
return opts.fallback ?? new Date(date).toLocaleDateString('ru');
}
}
return format(date, locale, opts);
}
import type { LocaleFunc } from 'timeago.js';
// Получить тип параметров функции локали
type LocaleFuncParams = Parameters<LocaleFunc>;
// [number: number, index: number]
// Получить тип возврата
type LocaleFuncReturn = ReturnType<LocaleFunc>;
// [string, string]
// Частично применённая функция локали (только index)
type IndexedLocaleFunc = (index: number) => [string, string];
import type { LocaleFunc } from 'timeago.js';
// Расширенная функция локали с метаданными
type LocaleFuncWithMeta = LocaleFunc & {
readonly localeCode: string;
readonly pluralForms: number;
validate?: () => boolean;
};
function createLocale(
localeCode: string,
fn: LocaleFunc,
pluralForms = 3
): LocaleFuncWithMeta {
const extended = fn as LocaleFuncWithMeta;
Object.defineProperties(extended, {
localeCode: { value: localeCode, writable: false },
pluralForms: { value: pluralForms, writable: false },
validate: { value: () => true, writable: true },
});
return extended;
}
type SupportedLocale = 'ru' | 'en_US' | 'de';
// Конфиг, где каждая локаль обязательна
type LocaleRegistry = {
[K in SupportedLocale]: import('timeago.js').LocaleFunc;
};
const registry: LocaleRegistry = {
ru: (n, i) => ['секунду назад', 'через секунду'],
en_US: (n, i) => ['just now', 'right now'],
de: (n, i) => ['gerade eben', 'gleich'],
};
type IsRTLLocale<L extends string> =
L extends 'ar' | 'he' | 'fa' | 'ur' ? true : false;
type LocaleDirection<L extends string> =
IsRTLLocale<L> extends true ? 'rtl' : 'ltr';
function getDirection<L extends string>(locale: L): LocaleDirection<L> {
const rtlLocales = new Set(['ar', 'he', 'fa', 'ur']);
return (rtlLocales.has(locale) ? 'rtl' : 'ltr') as LocaleDirection<L>;
}
// Использование
const dir = getDirection('ar'); // тип: 'rtl'
const ltr = getDirection('ru'); // тип: 'ltr'
type Language = 'ru' | 'en' | 'de' | 'fr' | 'zh';
type Region = 'RU' | 'US' | 'DE' | 'FR' | 'CN' | 'TW';
type LocaleKey = `${Language}_${Region}`;
// 'ru_RU' | 'en_US' | 'de_DE' | ...
// Функция с проверкой ключа локали
function registerLocale(key: LocaleKey, fn: import('timeago.js').LocaleFunc): void {
import('timeago.js').then(({ register }) => register(key, fn));
}
// Создать собственное пространство имён поверх timeago.js
namespace Timeago {
export type Locale = 'ru' | 'en_US' | 'de';
export interface Config {
locale: Locale;
relativeDate?: Date;
fallback?: string;
}
export interface FormattedDate {
raw: Date;
formatted: string;
locale: Locale;
}
}
import { format } from 'timeago.js';
function formatWithConfig(
date: Date | string | number,
config: Timeago.Config
): Timeago.FormattedDate {
return {
raw: new Date(date as any),
formatted: format(date, config.locale, { relativeDate: config.relativeDate }),
locale: config.locale,
};
}
import { format } from 'timeago.js';
// Тип, принимающий только будущие даты
type FutureDate = Date & { _future: true };
function toFutureDate(d: Date): FutureDate {
if (d.getTime() <= Date.now()) {
throw new TypeError('Date must be in the future');
}
return d as FutureDate;
}
function formatFuture(date: FutureDate, locale = 'ru'): string {
return format(date, locale);
}
import { format } from 'timeago.js';
// Извлечь тип первого аргумента format
type FormatInput = Parameters<typeof format>[0];
// Date | string | number
// Извлечь тип возвращаемого значения
type FormatOutput = ReturnType<typeof format>;
// string
// Создать типизированную обёртку с теми же сигнатурами
type FormatWrapper = (...args: Parameters<typeof format>) => FormatOutput;
const wrappedFormat: FormatWrapper = (date, locale, opts) => {
return format(date, locale, opts);
};
| Подход | Когда использовать |
|---|---|
| Declaration Merging | Изменение публичного API модуля |
| Intersection Types | Добавление свойств к существующему типу |
| Utility Types | Трансформация (Partial, Required, Pick) |
| Conditional Types | Типы, зависящие от значения другого типа |
| Template Literal Types | Строковые ключи с известной структурой |
| Namespace | Группировка связанных типов под одним именем |