Skip to content

Резолв расширений в лаунчере

Без обращения к сервису маркетплейса. Сервис каталога — 05-marketplace-service.md.

Поток загрузки

Точки входа (лаунчер + SDK):

  • обнаружение пакетов на диске (host);
  • initializeRyntExtensions@rynt/sdk/host;
  • перезагрузка реестра и маршрутов после установки (host).

1. Валидация манифеста

На каждом обнаруженном пакете:

  • parseRyntManifestJson
  • applyValidatedRyntContributesToManifest — при ошибке запись в manifestError (UI настроек) или пропуск в discovery

2. Индекс манифеста (локальный)

Реализовано в @rynt/sdk/extension (резолвер графа расширений).

ts
interface ExtensionManifestIndex {
  /** registryId → manifest.id владельца (единственный declares) */
  declaredBy: Map<string, string>;
  /** "registryId\0key" → manifest.id[] (несколько расширений могут заявить один слот) */
  slotClaims: Map<string, string[]>;
}

Построение:

  • из declaresRegistriesdeclaredBy
  • из extendsRegistries → для каждой пары (registryId, key) добавить manifest.id в slotClaims

flattenExtendsRegistries(manifest)

Вспомогательная функция только в SDK / лаунчере для индекса и отладки:

ts
// [{ registryId, key }, ...]
function flattenExtendsRegistries(
  contributes: ParsedRyntManifestContributes,
): Array<{ registryId: string; key: string }>;

Не дублировать как обязательный контракт сервиса: сервис при публикации сам разворачивает extendsRegistries в таблицу registry_slot_contributions.

2b. Проверка extensionApi (мягкая)

См. 08-extension-api-versioning.md.

  • parseExtensionApiMajor(manifest.extensionApi)required
  • EXTENSION_API_VERSION (хост) → host
  • required <= host → OK (в т.ч. старые расширения после обновления лаунчера)
  • required > host → запись в errors[id] с текстом «нужен более новый лаунчер»; по умолчанию не валить весь граф
  • legacy semver в манифесте → warn, major = 1

3. Проверка extensionDependencies

Для каждого расширения E и зависимости D из extensionDependencies:

  • D должен присутствовать в наборе включённых пакетов;
  • semver.satisfies(version(D), range) — иначе errors[E] = ….

Порядок загрузки строится топологической сортировкой графа зависимостей (resolveExtensionLoadOrder).

4. Implied dependencies

Для каждой записи в flattenExtendsRegistries(manifest):

registryIdДействие
core.*implied dep не нужен (ядро всегда доступно)
кастомныйнайти declaredBy.get(registryId) среди включённых расширений

Если владелец не найден → ошибка резолва: Registry "…" is not declared by any enabled extension.

Если владелец O найден, но O не указан в extensionDependencies потребителя Cдобавить ребро C → O в граф (и опционально warning в лог). Затем топосорт.

Конфликт: два расширения объявляют один registryId в declaresRegistries → ошибка графа.

5. Runtime store vs манифест

После всех setup:

  • Лаунчер не запрещает двум расширениям записать один (registryId, key) в store (поведение как сейчас: несколько записей с разным extensionId или перезапись по политике UI).
  • Soft-check (dev): для каждого (registryId, key) из extendsRegistries текущего id есть запись с extensionId === manifest.id → иначе console.warn.

6. Утилиты runtime (SDK)

APIНазначение
hasRegistryEntry(registryId, key)есть ли запись
getRegistryEntry(registryId, key)первая / явно выбранная запись (политика документируется в SDK)
resolveRegistrySlot(registryId, key){ status: 'present', row } | { status: 'missing' }

Реактивность: чтение в computed с зависимостью от extensionRegistriesRevision.

Ошибки

Код / сообщениеПричина
unknown extension dependencyid из extensionDependencies не установлен
semver mismatchверсия зависимости не входит в range
cycle detectedцикл в графе
registry not declaredextendsRegistries ссылается на несуществующий declare
duplicate registry declarationдва пакета declares один registryId

Ошибки возвращаются из loadOrReloadRyntExtensionsFromDisk как Record<extensionId, string> (+ __graph__ при фатальном графе).