Skip to content

Общие типы API (SDK)

Лаунчер и сервис маркетплейса используют один контракт TypeScript из SDK, без экспорта из корня @rynt/sdk.

Export path

@rynt/sdk/extension-marketplace

Subpath в @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: поиск каталога

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';

и не расходился с лаунчером.