Типизация контрактов между расширениями
Как описать expose и свои реестры так, чтобы зависимые пакеты получали автодополнение без as.
Два механизма
| Механизм | Модуль augmentation | API потребителя |
|---|---|---|
| Expose — публичные функции расширения | @rynt/sdk/extension-expose-map | useExtensionExpose('@rynt/…') |
| Реестры — тип строки реестра | @rynt/sdk/extension-registry-payload-map | getExtensionRegistry(MY_REGISTRY_ID) |
Оба subpath экспортируются из @rynt/sdk (см. SDK — обзор).
Expose (пакет-поставщик)
1. Опишите интерфейс API:
export interface ChatsCallsExtensionExpose {
sendMessageToConversation: (
conversationId: string,
payload: Record<string, unknown>,
textPreview: string,
) => Promise<void>;
}2. Дополните карту (в том же файле, что экспортируете зависимым пакетам):
declare module '@rynt/sdk/extension-expose-map' {
interface ExtensionExposeMap {
'@rynt/chats-calls': ChatsCallsExtensionExpose;
}
}3. В setup верните { expose: … } или вызовите ctx.defineExpose(…).
Ключ в ExtensionExposeMap = manifest.id расширения.
Реестры (пакет-владелец)
1. Константа id и тип строки:
export const CHATS_MESSAGE_TYPES_REGISTRY_ID =
'@rynt/chats-calls.message-types' as const;
export interface ChatMessageTypeRegistration {
body: Component;
}2. Augmentation (ключ = строковый registryId, не имя константы):
declare module '@rynt/sdk/extension-registry-payload-map' {
interface ExtensionRegistryPayloadMap {
'@rynt/chats-calls.message-types': ChatMessageTypeRegistration;
}
}3. В манифесте — contributes.rynt.declaresRegistries.
Пакет-потребитель
Side-effect import
Augmentation срабатывает при загрузке модуля с declare module. Рекомендуемый паттерн — env.d.ts:
/// <reference types="vite/client" />
import '@rynt/extension-chats-calls/types';npm-entry контракта
Поставщик публикует subpath в package.json:
{
"exports": {
"./types": {
"types": "./src/types.ts",
"default": "./src/types.ts"
}
}
}Потребитель импортирует типы и подключает augmentation:
import type { ChatInputToolbarItemProps } from '@rynt/extension-chats-calls/types';
import { useExtensionExpose } from '@rynt/sdk/extension';
const chats = useExtensionExpose('@rynt/chats-calls');
await chats.value?.sendMessageToConversation(/* … */);Достаточно одного side-effect import в env.d.ts — не обязательно импортировать types в каждом .vue.
Зависимость в манифесте
extensionDependencies: {
'@rynt/chats-calls': '^0.0.3',
},Гарантирует порядок setup: поставщик загрузится раньше.
Почему subpath, а не относительный путь
SDK импортирует ExtensionExposeMap через @rynt/sdk/extension-expose-map. Augmentation должно дополнять тот же модуль, иначе TypeScript видит пустой {}.
Не используйте устаревший путь @rynt/sdk/plugin — актуальный entry: @rynt/sdk/extension.
Fallback без augmentation
getExtensionRegistry<MyRow>('@rynt/my-pack.registry');или импорт типа MyRow напрямую из пакета владельца.
Пример в репозитории
- Поставщик: npm-пакет
@rynt/extension-chats-calls, entry./types - Потребитель: зависимое расширение с
env.d.ts(import '@rynt/extension-chats-calls/types') иuseExtensionExpose('@rynt/chats-calls')