Skip to content

Типизация контрактов между расширениями

Как описать expose и свои реестры так, чтобы зависимые пакеты получали автодополнение без as.

Два механизма

МеханизмМодуль augmentationAPI потребителя
Expose — публичные функции расширения@rynt/sdk/extension-expose-mapuseExtensionExpose('@rynt/…')
Реестры — тип строки реестра@rynt/sdk/extension-registry-payload-mapgetExtensionRegistry(MY_REGISTRY_ID)

Оба subpath экспортируются из @rynt/sdk (см. SDK — обзор).

Expose (пакет-поставщик)

1. Опишите интерфейс API:

ts
export interface ChatsCallsExtensionExpose {
  sendMessageToConversation: (
    conversationId: string,
    payload: Record<string, unknown>,
    textPreview: string,
  ) => Promise<void>;
}

2. Дополните карту (в том же файле, что экспортируете зависимым пакетам):

ts
declare module '@rynt/sdk/extension-expose-map' {
  interface ExtensionExposeMap {
    '@rynt/chats-calls': ChatsCallsExtensionExpose;
  }
}

3. В setup верните { expose: … } или вызовите ctx.defineExpose(…).

Ключ в ExtensionExposeMap = manifest.id расширения.

Реестры (пакет-владелец)

1. Константа id и тип строки:

ts
export const CHATS_MESSAGE_TYPES_REGISTRY_ID =
  '@rynt/chats-calls.message-types' as const;

export interface ChatMessageTypeRegistration {
  body: Component;
}

2. Augmentation (ключ = строковый registryId, не имя константы):

ts
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:

ts
/// <reference types="vite/client" />

import '@rynt/extension-chats-calls/types';

npm-entry контракта

Поставщик публикует subpath в package.json:

json
{
  "exports": {
    "./types": {
      "types": "./src/types.ts",
      "default": "./src/types.ts"
    }
  }
}

Потребитель импортирует типы и подключает augmentation:

ts
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.

Зависимость в манифесте

ts
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

ts
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')

См. также