Общие типы API (SDK)
Лаунчер и сервис маркетплейса используют один контракт TypeScript из SDK, без экспорта из корня @rynt/sdk.
Export path
@rynt/sdk/extension-marketplaceSubpath в @rynt/sdk:
json
"./extension-marketplace": {
"types": "./src/extension-marketplace/index.ts",
"default": "./src/extension-marketplace/index.ts"
}Extension API
ts
/** Текущая версия контракта платформы (целое). */
export const EXTENSION_API_VERSION = 1;
export function parseExtensionApiMajor(value: unknown): number | null;Манифест: extensionApi: "1". См. 08-extension-api-versioning.md.
Типы манифеста (общие с резолвером)
ts
export interface RegistryDeclaration {
id: string;
title?: string;
description?: string;
}
export interface RegistryExtensionContribution {
registryId: string;
keys: string[];
}
export interface RyntManifestContributesRynt {
declaresRegistries?: RegistryDeclaration[];
extendsRegistries?: RegistryExtensionContribution[];
}
/** Плоская пара для индекса лаунчера */
export interface RegistrySlotRef {
registryId: string;
key: string;
}
export function flattenExtendsRegistries(
contributes: RyntManifestContributesRynt | undefined,
): RegistrySlotRef[];Типы совпадают с полями contributes.rynt в манифесте — см. контракт манифеста.
REST: поиск слота
GET /api/v1/extensions/slots
Query:
ts
export interface MarketplaceSlotQuery {
registryId: string;
key: string;
}Response:
ts
export interface MarketplaceExtensionSummary {
id: string; // manifest.id
name: string;
version: string;
description?: string;
iconUrl?: string;
authorName?: string;
downloadUrl: string;
/** sha256 артефакта .ryntextension, опционально */
artifactSha256?: string;
}
export type MarketplaceSlotLookupResult =
| { kind: 'none' }
| { kind: 'one'; extension: MarketplaceExtensionSummary }
| { kind: 'many'; extensions: MarketplaceExtensionSummary[] };Соответствует UI:
none→ «Расширение для этого слота не найдено»one→ установить одним кликомmany→ диалог выбора (см. 06-launcher-ui.md)
REST: карточка расширения
GET /api/v1/extensions/:id
GET /api/v1/extensions/:id/versions/:version
ts
export interface MarketplaceExtensionDetail extends MarketplaceExtensionSummary {
manifest: Record<string, unknown>; // полный manifest.json
/** Rich-описание (TipTap HTML) */
listingDescriptionHtml?: string;
extensionDependencies?: Record<string, string>;
author?: { userId: string; displayName?: string };
publishedAt: string; // ISO
declaresRegistries: RegistryDeclaration[];
extendsRegistries: RegistryExtensionContribution[];
}
export interface ExtensionListingMeta {
iconUrl?: string;
authorName?: string;
listingDescriptionHtml?: string;
}
export interface UpdateExtensionListingRequest {
listingDescriptionHtml?: string;
}REST: поиск каталога
GET /api/v1/extensions/search
ts
export interface MarketplaceSearchQuery {
q?: string;
registryId?: string;
limit?: number;
offset?: number;
}
export interface MarketplaceSearchResponse {
items: MarketplaceExtensionSummary[];
total: number;
}REST: публикация (защищённый)
POST /api/v1/extensions
ts
export interface PublishExtensionRequest {
manifest: Record<string, unknown>;
downloadUrl: string;
artifactSha256?: string;
}
export interface PublishExtensionResponse {
id: string;
version: string;
publishedAt: string;
}Ошибки (типы для клиента):
ts
export type MarketplaceApiErrorCode =
| 'VALIDATION_ERROR'
| 'SLOT_ALREADY_CLAIMED' // (registryId, key) занят другим расширением
| 'DUPLICATE_VERSION'
| 'UNAUTHORIZED';
export interface MarketplaceApiError {
code: MarketplaceApiErrorCode;
message: string;
details?: Record<string, unknown>;
}SLOT_ALREADY_CLAIMED — тело, например:
json
{
"code": "SLOT_ALREADY_CLAIMED",
"message": "Slot core.modProvider/optifine already registered by @rynt/other",
"details": {
"registryId": "core.modProvider",
"key": "optifine",
"existingExtensionId": "@rynt/other"
}
}Клиент SDK (лаунчер)
ts
export interface ExtensionMarketplaceClientOptions {
baseUrl: string;
getAuthToken?: () => string | Promise<string | null>;
}
export interface ExtensionMarketplaceClient {
lookupSlot(query: MarketplaceSlotQuery): Promise<MarketplaceSlotLookupResult>;
getExtension(id: string, version?: string): Promise<MarketplaceExtensionDetail>;
search(query: MarketplaceSearchQuery): Promise<MarketplaceSearchResponse>;
}Реализация: fetch + разбор JSON с проверкой формы (Zod в SDK опционально для runtime parse).
Zod / валидация
Рекомендация: экспортировать из того же модуля схемы Zod как marketplaceManifestContributesSchema, publishExtensionRequestSchema, чтобы сервис делал:
ts
import { publishExtensionRequestSchema } from '@rynt/sdk/extension-marketplace';и не расходился с лаунчером.