Skip to content

Чеклист создания Package

Руководство по созданию нового пакета в packages/.


Когда создавать Package

Создавайте package когда:

  • ✅ Код используется в нескольких интеграциях
  • ✅ Это обёртка над внешним API (CRM, LLM, Vector DB)
  • ✅ Это абстракция инфраструктуры (DB, Cache, Creds)
  • ✅ Код должен быть независимо версионируемым

Не создавайте package когда:

  • ❌ Код используется только в одной интеграции
  • ❌ Это бизнес-логика специфичная для интеграции
  • ❌ Код слишком простой (можно просто скопировать)

Структура Package

packages/my-package/
├── package.json
├── tsconfig.json
├── tsup.config.ts
├── README.md              # Документация (симлинк в docs/packages/)
└── src/
    ├── index.ts           # Экспорты
    ├── client.ts          # Основной клиент
    └── types.ts           # TypeScript типы

Чеклист

1. Создание структуры

bash
# Создать папку
mkdir -p packages/my-package/src

# Создать файлы
touch packages/my-package/package.json
touch packages/my-package/tsconfig.json
touch packages/my-package/tsup.config.ts
touch packages/my-package/src/index.ts
touch packages/my-package/src/client.ts
touch packages/my-package/src/types.ts
touch packages/my-package/README.md

2. package.json

json
{
	"name": "@happ-integ/my-package",
	"version": "0.0.1",
	"type": "module",
	"main": "./dist/index.js",
	"types": "./dist/index.d.ts",
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"import": "./dist/index.js"
		}
	},
	"scripts": {
		"build": "tsup",
		"dev": "tsup --watch",
		"typecheck": "tsc --noEmit"
	},
	"dependencies": {
		// ...
	},
	"devDependencies": {
		"tsup": "^8.0.0",
		"typescript": "^5.0.0"
	}
}

3. tsconfig.json

json
{
	"extends": "../../tsconfig.json",
	"compilerOptions": {
		"outDir": "./dist",
		"rootDir": "./src"
	},
	"include": ["src/**/*"]
}

4. tsup.config.ts

typescript
import { defineConfig } from "tsup";

export default defineConfig({
	entry: ["src/index.ts"],
	format: ["esm"],
	dts: true,
	clean: true,
	sourcemap: true,
});

5. Код

src/types.ts:

typescript
export interface IMyClientConfig {
	apiKey: string;
	baseUrl?: string;
}

export interface IMyClientResult {
	success: boolean;
	data?: any;
}

src/client.ts:

typescript
import type { IMyClientConfig, IMyClientResult } from "./types";

export class MyClient {
	private apiKey: string;
	private baseUrl: string;

	constructor(config: IMyClientConfig) {
		this.apiKey = config.apiKey;
		this.baseUrl = config.baseUrl ?? "https://api.example.com";
	}

	async doSomething(): Promise<IMyClientResult> {
		// ...
	}

	async healthCheck(): Promise<boolean> {
		try {
			// Проверка соединения
			return true;
		} catch {
			return false;
		}
	}
}

src/index.ts:

typescript
export * from "./types";
export * from "./client";

6. Документация (README.md)

markdown
# @happ-integ/my-package

Описание пакета.

## Установка

\`\`\`bash
pnpm add @happ-integ/my-package
\`\`\`

## Использование

\`\`\`typescript
import { MyClient } from "@happ-integ/my-package";

const client = new MyClient({
apiKey: env.MY_API_KEY,
});

const result = await client.doSomething();
\`\`\`

## API

### MyClient

\`\`\`typescript
new MyClient(config: IMyClientConfig)
\`\`\`

**Config:**

| Параметр  | Тип      | Описание               |
| --------- | -------- | ---------------------- |
| `apiKey`  | `string` | API ключ               |
| `baseUrl` | `string` | Base URL (опционально) |

### Методы

#### `doSomething(): Promise<IMyClientResult>`

Описание метода.

#### `healthCheck(): Promise<boolean>`

Проверка соединения.

7. Симлинк для документации

bash
# Создать симлинк
ln -sf ../../packages/my-package/README.md docs/packages/my-package.md

8. Добавить в VitePress

Обновить docs/.vitepress/config.ts:

typescript
{
  text: "Packages",
  collapsed: false,
  items: [
    { text: "LLM", link: "/packages/llm" },
    { text: "Vector", link: "/packages/vector" },
    // ...
    { text: "My Package", link: "/packages/my-package" }, // Добавить
  ],
},

9. Установить зависимости

bash
pnpm install

10. Собрать

bash
pnpm --filter @happ-integ/my-package build

Финальный чеклист

  • [ ] Структура файлов создана
  • [ ] package.json с правильным именем и экспортами
  • [ ] tsconfig.json наследуется от корневого
  • [ ] tsup.config.ts настроен
  • [ ] Код клиента реализован
  • [ ] Типы вынесены в types.ts
  • [ ] healthCheck() метод добавлен
  • [ ] README.md с описанием и примерами
  • [ ] Симлинк в docs/packages/ создан
  • [ ] VitePress конфиг обновлён
  • [ ] Package собирается без ошибок
  • [ ] Package можно использовать в интеграции

Соглашения по именованию

ЧтоФорматПример
Имя пакета@happ-integ/kebab-case@happ-integ/google-drive
Класс клиентаPascalCaseClientGoogleDriveClient
Типы/ИнтерфейсыIPascalCaseIGoogleDriveConfig
Файлыkebab-case.tsgoogle-drive.service.ts

Паттерны

Абстракция с провайдерами (DB, Cache, LLM, Vector)

typescript
import { Logger } from "@happ-integ/logger";

const logger = new Logger("my-service");

export class MyService {
	private primary: Provider;
	private secondary?: Provider;

	constructor(config: IConfig) {
		this.primary = this.getProvider(config.primary);
		if (config.secondary) {
			this.secondary = this.getProvider(config.secondary);
		}
	}

	async doSomething() {
		try {
			return await this.primary.doSomething();
		} catch (error) {
			if (!this.secondary) throw error;
			logger.warn("Primary failed, falling back...");
			return await this.secondary.doSomething();
		}
	}
}

CRM/API клиент

typescript
export class MyCRMClient {
	constructor(private apiKey: string) {}

	private async request<T>(path: string, options?: RequestInit): Promise<T> {
		const response = await fetch(`${BASE_URL}${path}`, {
			...options,
			headers: {
				Authorization: `Bearer ${this.apiKey}`,
				"Content-Type": "application/json",
				...options?.headers,
			},
		});
		if (!response.ok) {
			throw new Error(`HTTP ${response.status}`);
		}
		return response.json();
	}

	async getRecord(id: string) {
		return this.request<IRecord>(`/records/${id}`);
	}
}

Пакет с автоматическим разрешением секретов из env

Для пакетов, которые используют глобальные секреты из Doppler (API ключи, URL и т.д.), используйте паттерн с env:

typescript
// types.ts
export interface IMyServiceEnv {
	MY_API_KEY?: string;
	MY_API_URL?: string;
}

export interface IMyServiceConfig {
	apiKey?: string;
	apiUrl?: string;
	env?: IMyServiceEnv;
}

// client.ts
export class MyService {
	private apiKey: string;
	private apiUrl: string;

	constructor(config: IMyServiceConfig) {
		// Приоритет: явный параметр > env binding
		const apiKey = config.apiKey || config.env?.MY_API_KEY;
		const apiUrl = config.apiUrl || config.env?.MY_API_URL;

		if (!apiKey) {
			throw new Error("MY_API_KEY is required");
		}

		this.apiKey = apiKey;
		this.apiUrl = apiUrl || "https://api.example.com";
	}
}

Использование:

typescript
// В интеграции — просто передаём env
const service = new MyService({ env });

// Или явно передаём ключ (для тестов или особых случаев)
const service = new MyService({ apiKey: "sk-..." });

Важно:

  • НЕ используйте process.env — оно не работает в Cloudflare Workers
  • Секреты из Doppler автоматически доступны через env binding