Перейти к содержимому

24. Библиотечные API

CLI, расширения редактора и Viewer построены на трёх опубликованных пакетах: @archlang/parser, @archlang/core и @archlang/lsp. Если вы строите что-то новое — свой валидатор, CI-бота, генератор документации, интеграцию для платформы не на JS — вы будете напрямую использовать один или несколько из этих пакетов.

Эта глава для авторов инструментов. Предполагается знакомство с JavaScript / TypeScript и Node.js. Если вы потребляете .arch файлы только через редактор и CLI, переходите к Главе 25.

Три пакета

ПакетЧто даётКогда использовать
@archlang/parserТекст исходника → AST + лексерНужен только синтаксис, без разрешения
@archlang/coreAST → разрешённая модель, валидация, граф, дифф, рендер описанийНужна полная каноническая модель
@archlang/lspЗагрузчик пакетов + языковой сервер + хелпер форматированияНужна загрузка из файловой системы, сервер для редактора или каноническое форматирование

core зависит от parser. lsp зависит от core. Выбирайте самый высокий уровень, который вам нужен.

Стоит знать о границе пакетов: loadPackage живёт в @archlang/lsp, а не в core. Он обрабатывает обход файловой системы и разрешение зависимостей, что важно только когда есть рабочее пространство для чтения. Чистый анализ уже распарсенных исходников использует только core.

@archlang/parser

Окно терминала
npm install @archlang/parser
import { parse } from "@archlang/parser";
const source = `
service Payments {
team: Payments
command Authorize
}
`;
const result = parse(source, "/main.arch");
// result.file — ParsedFile (declarations, uses, manifest if any)
// result.diagnostics — Diagnostic[] (parse errors, each with span + message + code)

parse(source: string, file?: string) — чистая функция из текста исходника в AST распарсенного файла плюс диагностики. Необязательный параметр file записывается на каждый span AST, чтобы вышестоящий инструментарий мог находить ошибки. Никакого разрешения имён, никакой валидации сверх синтаксической корректности, никакой межфайловой работы.

Типичные применения:

  • Подсветка синтаксиса в среде без LSP.
  • Инструменты поиска по строке, обходящие исходник в поиске деклараций, соответствующих шаблону.
  • Миграционные скрипты, переписывающие текст исходника — парсят, чтобы найти позиции, затем патчат исходник напрямую.

Типы AST экспортируются из пакета: ModuleDecl, InterfaceDecl, FacetDecl, ProcessDecl, ViewDecl, TypeDecl, SubprocessDecl и сопутствующие типы span / значений / деклараций.

@archlang/core

Окно терминала
npm install @archlang/core

core потребляет распарсенные файлы, разрешает модель, запускает валидатор, вычисляет граф зависимостей и строит диффы. Это то, на чём строятся все вышестоящие инструменты.

Разрешение пакета

import { resolvePackage, type PackageInput, type PackageMap } from "@archlang/core";
const result = resolvePackage(input, deps);
// result.model — ResolvedModel (canonical resolved view)
// result.diagnostics — Diagnostic[] (resolution + validation diagnostics)

resolvePackage(pkg: PackageInput, deps: PackageMap) принимает распарсенный пакет (его манифест плюс распарсенные файлы) и карту его зависимостей. Применяет штамповку по шаблонам типов (Глава 15), структурный каскад (Глава 18) и выдаёт ResolvedModel.

ResolvedModel предоставляет массивы разрешённых модулей, фасетов, интерфейсов, процессов и проекций:

import type { ResolvedModel, ResolvedModule } from "@archlang/core";
function totalInterfaces(model: ResolvedModel): number {
return model.modules.reduce((sum, m) => sum + m.interfaces.length, 0);
}

Валидация

import { validate } from "@archlang/core";
const result = validate(model);
// result.diagnostics — array of validator diagnostics

validate выдаёт каждую диагностику, которую может породить валидатор: пропущенные обязательные поля, неверные ссылки в шагах процесса, нарушения инвариантов меток, провалы межпакетных ссылок. Коды стабильны; уровни важности могут быть переопределены конфигом проекта.

Построение графа зависимостей

import { buildGraph } from "@archlang/core";
const graph = buildGraph(model);
// graph.edges — DependencyEdge[] (one per derived call)
// graph.modulesById — Map<StableId, ResolvedModule>
// graph.adjacency — Map<StableId, StableId[]>

Каждое ребро графа берётся из шага процесса (или из разводки subscribes:). На графе работают проекции, анализ радиуса распространения и отчёты о влиянии.

Полезные хелперы в том же модуле:

import { getDependents, getDependencies, computeBlastRadius } from "@archlang/core";
const dependents = getDependents(graph, "#pay001");
const dependencies = getDependencies(graph, "#pay001");
const blastRadius = computeBlastRadius(graph, "#pay001");

Вычисление диффов

import { diffModels, type ChangeSet, type ModuleDiff } from "@archlang/core";
const delta: ChangeSet = diffModels(beforeModel, afterModel);
// delta.modules — ReadonlyMap<ModuleId, ModuleDiff>
// delta.processes — ReadonlyMap<ProcessId, ProcessDiff>
// delta.views — ReadonlyMap<ViewId, ViewDiff>

Каждый ModuleDiff несёт status (одно из added | removed | modified | renamed | unchanged) плюс изменения по каждому полю, фасету, интерфейсу. Итерация:

for (const [id, mDiff] of delta.modules) {
if (mDiff.status === "renamed") {
console.log(`Renamed: ${mDiff.fromName}${mDiff.toName} (${id})`);
} else if (mDiff.status === "added") {
console.log(`Added: ${mDiff.toName} (${id})`);
} else if (mDiff.status === "removed") {
console.log(`Removed: ${mDiff.fromName} (${id})`);
}
}

Это движок, который использует режим диффа Viewer (Глава 14). Конвейеры, которым нужны машиночитаемые архитектурные дельты, потребляют diffModels напрямую.

Рендер описаний

import { renderDescription, makeDescriptionContext } from "@archlang/core";
const ctx = makeDescriptionContext(model, { node: someResolvedModule });
const html = renderDescription(rawDescriptionText, ctx);

renderDescription — тот же рендерер, который LSP использует для всплывающих подсказок, а Viewer — для тултипов. makeDescriptionContext строит объект контекста (поиск меток, резолвер [[ref]]). Используйте их при сборке страниц документации, которым нужен паритет с отображением в редакторе.

@archlang/lsp

Окно терминала
npm install @archlang/lsp

Пакет LSP служит трём целям:

  1. Загрузка пакетов из источника, похожего на файловую систему.
  2. Запуск языкового сервера.
  3. Каноническое форматирование текста исходника.

Загрузка пакета

import { loadPackage, type LoadedPackage } from "@archlang/lsp";
const pkg: LoadedPackage = await loadPackage(io, "/path/to/package");

loadPackage обходит корень пакета, парсит каждый .arch файл, рекурсивно загружает зависимости и возвращает LoadedPackage (PackageInput плюс PackageMap разрешённых зависимостей). Соедините с resolvePackage из core:

import { loadPackage } from "@archlang/lsp";
import { resolvePackage, validate } from "@archlang/core";
import { NodeIO } from "@archlang/lsp/node";
const io = new NodeIO();
const loaded = await loadPackage(io, "/path/to/package");
const resolved = resolvePackage(loaded.input, loaded.deps);
const validation = validate(resolved.model);

Интерфейс LspIO абстрагирует доступ к файловой системе. Реализации для Node и для браузера удовлетворяют ему; передавайте ту, что подходит вашей среде.

Форматирование исходника

import { formatSource } from "@archlang/lsp";
const result = formatSource(originalSource);
// result.text — formatted source
// result.changed — boolean: did anything change?

formatSource(input: string): FormatSourceResult — чистая функция из текста исходника в канонически отформатированный текст. Полезна для pre-commit хуков, форматирования при сохранении в кастомных редакторах и инструментов генерации, которые выдают .arch исходник.

Запуск языкового сервера

// Node-side, stdio (what editor extensions use)
import { startNodeServer } from "@archlang/lsp/node";
startNodeServer();
// Browser-side, web worker (what the hosted demo uses)
import { startBrowserServer } from "@archlang/lsp/browser";
startBrowserServer(virtualFileSystem);

Вы вызываете это напрямую только если строите новую интеграцию редактора. Существующие расширения сами обрабатывают разводку. Поверхность протокола — стандартный LSP: initialize, textDocument/completion, textDocument/hover и т.д.

Разобранный пример: CI-бот, комментирующий архитектурные дельты PR

import { loadPackage } from "@archlang/lsp";
import { NodeIO } from "@archlang/lsp/node";
import { resolvePackage, diffModels } from "@archlang/core";
async function describeDelta(beforePath: string, afterPath: string): Promise<string> {
const io = new NodeIO();
const beforeLoaded = await loadPackage(io, beforePath);
const afterLoaded = await loadPackage(io, afterPath);
const before = resolvePackage(beforeLoaded.input, beforeLoaded.deps).model;
const after = resolvePackage(afterLoaded.input, afterLoaded.deps).model;
const delta = diffModels(before, after);
const lines: string[] = [];
for (const [id, m] of delta.modules) {
if (m.status === "renamed") {
lines.push(`- **Renamed:** \`${m.fromName}\`\`${m.toName}\``);
} else if (m.status === "added") {
lines.push(`- **Added module:** \`${m.toName}\` (\`${id}\`)`);
} else if (m.status === "removed") {
lines.push(`- **Removed module:** \`${m.fromName}\` (\`${id}\`)`);
}
}
return lines.join("\n");
}

Примерно 30 строк связующего кода. Сводка архитектурной дельты в PR теперь автоматическая.

Версионирование

Все три пакета следуют семантическому версионированию. Внутри линии 0.x поверхность протокола LSP и коды диагностик считаются полустабильными: добавления — минорные; переименования или удаления — минорные с явными отметками в журнале изменений.

Форма AST и форма разрешённой модели считаются нестабильными внутри 0.x. Авторам инструментов, строящим на их основе, стоит зафиксировать конкретную версию и перетестировать при каждом апгрейде. Формы стабилизируются в 1.0.

Чего нет в библиотеках

  • Нет рендерера. Рендер диаграмм происходит в @archlang/web. Библиотечные API дают модель и граф; рендер — отдельная забота.
  • Нет интеграции с git. loadPackage читает через интерфейс LspIO. Инструменты, осведомлённые о git (коммитят снимки, считают дельты веток), живут в CLI и в пользовательском коде.
  • Нет HTTP API. Ни один из трёх пакетов не открывает порт. Если нужна сервисная поверхность — оберните библиотеку в свой сервер.

Итог

  • @archlang/parser — исходник → AST + лексер.
  • @archlang/core — AST → разрешённая модель + валидация + граф + дифф + рендер описаний.
  • @archlang/lsp — загрузчик пакетов (loadPackage), языковой сервер (startNodeServer, startBrowserServer), форматтер исходника (formatSource).
  • loadPackage живёт в @archlang/lsp, потому что ему нужен LspIO (файловая система); соединяйте с resolvePackage из core для анализа.
  • Формы AST и разрешённой модели нестабильны внутри 0.x; LSP и коды диагностик полустабильны.

Что дальше

Глава 25: SaaS-бэкенд → — открывает часть VI, шесть разобранных проектов с реалистичными системами, смоделированными в Archlang.