Резолв расширений в лаунчере
Без обращения к сервису маркетплейса. Сервис каталога — 05-marketplace-service.md.
Поток загрузки
Точки входа (лаунчер + SDK):
- обнаружение пакетов на диске (host);
initializeRyntExtensions—@rynt/sdk/host;- перезагрузка реестра и маршрутов после установки (host).
1. Валидация манифеста
На каждом обнаруженном пакете:
parseRyntManifestJsonapplyValidatedRyntContributesToManifest— при ошибке запись вmanifestError(UI настроек) или пропуск в discovery
2. Индекс манифеста (локальный)
Реализовано в @rynt/sdk/extension (резолвер графа расширений).
interface ExtensionManifestIndex {
/** registryId → manifest.id владельца (единственный declares) */
declaredBy: Map<string, string>;
/** "registryId\0key" → manifest.id[] (несколько расширений могут заявить один слот) */
slotClaims: Map<string, string[]>;
}Построение:
- из
declaresRegistries→declaredBy - из
extendsRegistries→ для каждой пары(registryId, key)добавитьmanifest.idвslotClaims
flattenExtendsRegistries(manifest)
Вспомогательная функция только в SDK / лаунчере для индекса и отладки:
// [{ 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)→requiredEXTENSION_API_VERSION(хост) →hostrequired <= 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 dependency | id из extensionDependencies не установлен |
semver mismatch | версия зависимости не входит в range |
cycle detected | цикл в графе |
registry not declared | extendsRegistries ссылается на несуществующий declare |
duplicate registry declaration | два пакета declares один registryId |
Ошибки возвращаются из loadOrReloadRyntExtensionsFromDisk как Record<extensionId, string> (+ __graph__ при фатальном графе).