Сервис маркетплейса расширений
Отдельный backend-сервис каталога (REST + Postgres), не часть npm-пакета расширения.
Стек: Fastify, Postgres, node-pg-migrate, Zod, типы из @rynt/sdk/extension-marketplace.
Отличие от лаунчера: уникальность слота
| Среда | Политика (registryId, key) |
|---|---|
| Лаунчер (runtime store) | Несколько расширений могут записать один слот; UI/политика хоста не навязывает глобальную уникальность |
| Сервис каталога | При публикации каждая пара (registry_id, registry_key) глобально уникальна в каталоге |
При POST /api/v1/extensions сервис:
- Парсит и валидирует manifest (
contributes.ryntбезrequiresRegistries,extensionApi: "1"— см. 08-extension-api-versioning.md). - Разворачивает
extendsRegistriesв строки(registry_id, registry_key). - Для каждой строки проверяет отсутствие записи в
registry_slot_contributionsс другимextension_id. - При конфликте → 409 +
MarketplaceApiErrorсcode: SLOT_ALREADY_CLAIMED.
Обновление версии того же manifest.id: слоты предыдущей версии снимаются и записываются заново из нового manifest (транзакция).
Повторная публикация той же версии → DUPLICATE_VERSION.
Схема БД
extensions
| Колонка | Описание |
|---|---|
id, version | PK составной (id, version) |
manifest | полный JSON |
download_url | URL .ryntextension |
is_latest | boolean, одна актуальная версия на id для каталога |
author_user_id | Keystone user id (текст), nullable для системных пакетов |
Индекс: GIN по manifest или tsvector по name + description.
registry_declarations
Денормализация declaresRegistries. PK (extension_id, registry_id).
Уникальность: один registry_id в каталоге может быть объявлен только одним extension_id (constraint на registry_id UNIQUE) — зеркало правила лаунчера «один владелец реестра».
registry_slot_contributions
Денормализация extendsRegistries (развернуть все keys).
| Constraint | Назначение |
|---|---|
UNIQUE (registry_id, registry_key) | один слот — одно расширение в каталоге |
PK (extension_id, registry_id, registry_key) | все слоты пакета |
Индекс для поиска: (registry_id, registry_key) — основной запрос кнопки слота.
REST API
Контракт типов: 04-sdk-shared-types.md.
Публичные
| Method | Path | Описание |
|---|---|---|
| GET | /api/v1/extensions/slots?registryId=&key= | MarketplaceSlotLookupResult |
| GET | /api/v1/extensions/search?q=®istryId=&limit=&offset= | поиск |
| GET | /api/v1/extensions/:id | latest или список версий |
| GET | /api/v1/extensions/:id/versions/:version | деталь |
| GET | /api/v1/registries/:registryId | кто объявил реестр + метаданные |
Защищённые
Authorization: Bearer <sessionToken> (сессия Keystone). Чтение каталога без токена.
| Method | Path | Описание |
|---|---|---|
| POST | /api/v1/extensions | публикация |
| PUT | /api/v1/extensions/:id/versions/:version | обновить url / manifest |
| DELETE | /api/v1/extensions/:id/versions/:version | снять с каталога |
Публикация: алгоритм проверки слотов
FOR each (registryId, key) IN flatten(manifest.contributes.rynt.extendsRegistries):
IF EXISTS registry_slot_contributions
WHERE registry_id = registryId AND registry_key = key
AND extension_id <> manifest.id:
RAISE SLOT_ALREADY_CLAIMED
INSERT extensions ...
INSERT registry_declarations ...
INSERT registry_slot_contributions ...declaresRegistries:
FOR each registryId IN declares:
IF EXISTS registry_declarations WHERE registry_id = registryId AND extension_id <> manifest.id:
RAISE REGISTRY_ALREADY_DECLAREDПоиск
- Точный слот —
registry_slot_contributionsJOINextensionsWHEREis_latest = true. - По реестру — все contributions с
registry_id = $1. - Текст —
search?q=по name/description + опционально filterregistryId.
Redis — post-MVP.
Переменные окружения
| Env | Описание |
|---|---|
DATABASE_URL | Postgres |
PORT | HTTP |
JWT_SECRET или AUTH_INTROSPECT_URL | проверка Bearer для POST |
Миграции
src/db/migrations/ + скрипты migrate:up / migrate:create как в chat-service.
Первая миграция: таблицы выше + constraints UNIQUE.
Связь с лаунчером
Лаунчер не вызывает сервис при обычной загрузке расширений с диска. Сервис нужен для:
- кнопки / диалога установки слота;
- глобального диалога каталога;
- будущего publish CLI / CI.
Установка: downloadUrl → скачивание файла → установка .ryntextension в каталог расширений → перезагрузка реестра в лаунчере.