@lowcode/runtime-core
@lowcode/runtime-core — это движок выполнения сгенерированных приложений, общий для:
- превью DSL-приложений в builder-web;
- будущего отдельного runtime-host SPA;
- любых сред, где нужно исполнить React-бандл, сгенерированный
@lowcode/dsl-compiler.
Runtime-core почти ничего не знает про конкретный UI, но знает всё про:
- структуру рантайм-состояния (
RuntimeSnapshot), - типы команд (
RuntimeCommand), - протокол причин изменений (
RuntimeChangeReason), - контракт инстанса (
RuntimeInstance), - контекст компонентов и действий (
RuntimeContext), - работу с источниками данных (
RuntimeDataSourceExecutor+ snapshot.dataSources), - базовые devtools-хелперы (форматирование причин изменений, фильтрация событий, React-хуки).
Этот README описывает:
- архитектуру runtime-core;
- структуру файлов и модулей;
- ключевые сущности (snapshot, команды, actions, components, devtools, dataSources);
- как runtime-core используется в builder-web;
- как он будет использоваться в отдельном runtime-host SPA;
- план развития runtime-core и runtime-host.
1. Назначение runtime-core
Runtime-core обеспечивает общий исполнительный слой для DSL-приложений:
- принимает на вход сгенерированный бандл (TSX/JSX/TS/JS-файлы);
- компилирует его в JS-модули и исполняет в изолированном окружении;
- предоставляет реестр визуальных компонентов (React-компоненты);
- предоставляет реестр действий (actions) для императивных операций;
- ведёт runtime-состояние приложения (глобальный state, состояние страниц, источники данных);
- принимает и обрабатывает типизированные команды (
RuntimeCommand); - уведомляет хост (builder-web / runtime-host SPA) о всех изменениях через
RuntimeChangeReasonи devtools-подписки; - делегирует фактическое выполнение источников данных во внешний executor (
RuntimeDataSourceExecutor).
Главная цель: один рантайм для всех окружений, чтобы DSL-приложение вело себя одинаково в превью и в настоящем runtime-host.
2. Структура пакета
packages/runtime-core/
├─ src/
│ ├─ index.ts # Публичный API: типы, createRuntimeInstance, хуки, devtools
│ ├─ core/
│ │ ├─ compileBundle.ts # TSX/JSX/TS/JS → CommonJS JS-модули (через @babel/standalone)
│ │ ├─ executeBundle.ts # Выполнение JS-модулей и поиск App-компонента
│ │ └─ moduleLoader.ts # In-memory загрузчик модулей (require + module.exports)
│ ├─ react/
│ │ ├─ RuntimeRoot.tsx # React-обёртка над App-компонентом + RuntimeReactContext
│ │ ├─ createReactRuntimeInstance.ts # Реализация createRuntimeInstance для React
│ │ └─ runtimeHooks.ts # Набор React-хуков для snapshot/state/dataSources
│ ├─ state/
│ │ └─ runtimeState.ts # Внутреннее состояние рантайма, snapshot + listeners + команды
│ ├─ runtime/
│ │ ├─ defaultDataSourceExecutor.ts # Дефолтный executor источников данных с поддержкой моков
│ │ ├─ watcherEngine.ts # Page watchers: lastValue, debounce/throttle, запуск actions
│ ├─ types/
│ │ └─ runtimeDataSourceExecutor.ts # Публичный контракт для executor’а источников данных
│ ├─ devtools/
│ │ └─ runtimeDevtools.ts # Devtools-хелперы: форматтер, type-guards, subscribeToRuntimeEvents
│ └─ components/ # (опционально) dev-компоненты для превью
├─ dist/
├─ package.json
└─ README.md # Текущий файл
Структура может немного меняться по мере развития, но основные слои остаются теми же: core (компиляция/исполнение), state/runtime (состояние и команды), react (обёртка и хуки), runtime (executor источников данных), devtools (инструменты для инспектора и логов).
3. Ключевые сущности
3.1. RuntimeSnapshot
RuntimeSnapshot — это «снимок» текущего состояния рантайма.
Он включает:
export interface RuntimeSnapshot {
globalState: Record<string, unknown>;
pageState: Record<string, Record<string, unknown>>;
activePageId: string | null;
route: {
path?: string;
params: Record<string, string>;
query: Record<string, string>;
};
dataSources: Record<string, unknown>;
}
globalState— глобальное состояние приложения (аналогappStateв DSL);pageState— состояние по страницам (pageId → state);activePageId— текущая активная страница (илиnull);route— состояние роутинга (path + params + query);dataSources— состояние источников данных.
Состояние dataSources хранится по ключу dataSourceId и может включать:
- произвольные поля результата (
items,user,totalи т.п.); - служебные meta-поля, которые заполняет runtime-core:
__lastCallAt: number— последний момент вызоваcallDataSource;__lastRefetchAt: number— последний моментrefetchDataSource;__lastPayload: unknown— последний payload;__lastResultAt: number— последний успешный результат executor’а;
__lastErrorAt: number— последний момент ошибки;__lastError?: string— текст последней ошибки;__errorPayload?: unknown— полезная нагрузка ошибочного сценария (например, API mock payload);__errorStatusCode?: numberи__errorStatusText?: string— HTTP-код/подпись для API/сетевых ошибок;__errorScenario?: 'api' | 'network'— какой тип ошибочного мока отработал;__mockScenario?: 'success' | 'error'— если пользователь вручную переключал сценарий при последнем вызове.
Модель может быть уточнена в будущих версиях (например, явные статусы loading/success/error).
3.2. RuntimeCommand
Команда, которую внешний код может отправить в рантайм:
type RuntimeCommand =
| { type: 'navigate'; pageId: string; params?: Record<string, string>; query?: Record<string, string>; path?: string }
| { type: 'setState'; scope: 'global' | 'page'; pageId?; path: string[]; value: unknown }
| { type: 'refetchDataSource'; dataSourceId: string }
| { type: 'callDataSource'; dataSourceId: string; payload?: unknown }
| { type: 'reset' }
| { type: 'custom'; name: string; payload?: unknown };
Команды обрабатываются на уровне state/runtimeState.ts (функция applyRuntimeCommand), которая:
- формирует новый snapshot;
- записывает изменение в нужную область (
globalState,pageState,dataSources,activePageId); - вызывает
updateRuntimeSnapshot, который уведомляет всех подписчиков.
3.3. RuntimeChangeReason
Причина, по которой изменился snapshot. Используется для devtools и внешних подписчиков:
init— первичная инициализация;stateChanged— изменение глобального или страничного состояния;dataSourceCallStarted— запрос на получение данных из источника;dataSourceChanged— изменения, связанные с источниками данных;navigation— навигация между страницами;custom— произвольное пользовательское событие.
Devtools-утилиты (formatRuntimeChangeReason, type-guards) работают поверх этого типа.
3.4. RuntimeContext
Контекст выполнения рантайма:
export interface RuntimeContext {
components: RuntimeComponentsRegistry;
actions: RuntimeActionsRegistry;
}
components— реестр React-компонентов (имя → компонент);actions— реестр действий (имя → функция).
Этот контекст инжектируется в sandbox при выполнении скомпилированного бандла.
3.5. RuntimeInstance
Экземпляр рантайма — то, что получает хост (builder-web, runtime-host SPA):
export interface RuntimeInstance {
RootComponent: ComponentType;
getSnapshot(): RuntimeSnapshot;
subscribe(listener: (snapshot: RuntimeSnapshot, reason: RuntimeChangeReason) => void): () => void;
dispatch(command: RuntimeCommand): void;
}
Создаётся через createRuntimeInstance(options).
3.6. React-хуки
Пакет экспортирует набор React-хуков для работы с рантаймом:
useRuntimeSnapshot()— подписка на целыйRuntimeSnapshot;useRuntimeDispatch()— доступ кdispatchиз React-дерева;useGlobalState(path?: string[])— чтение/подписка на частьglobalState;usePageState(path?: string[], pageId?: string)— чтение/подписка на state страницы;usePageStateObject(pageId?: string)— получение всего state страницы;useDataSource(id: string)— чтение состояния конкретного DataSource поid.
Они работают поверх общего контекста RuntimeReactContext, который создаётся в RuntimeRoot.
3.7. Watcher-engine
watcherEngine — утилита runtime-core для page watchers:
- хранит
lastValueпо каждому watcher; - сравнивает значения (
Object.is); - поддерживает
mode: "change" | "always"; - поддерживает
debounceMsиthrottleMs; - умеет
cleanupPage(pageId)при выходе со страницы.
Используется сгенерированным App.tsx (dsl-compiler) для реакций вида:
route.params.id → callDataSource.
Важно: режим always допускается только при ненулевом debounceMs или throttleMs.
3.8. Devtools-хелперы
В devtools/runtimeDevtools.ts находятся утилиты:
-
formatRuntimeChangeReason(reason)— возвращает англоязычную строку с описанием причины изменения (для логов/UI); -
type-guards:
isNavigationChange(reason);isStateChangedReason(reason);isDataSourceChangedReason(reason);isCustomChangeReason(reason);
-
subscribeToRuntimeEvents(runtime, options)— обёртка надruntime.subscribeс фильтрацией по видам событий и произвольномуpredicate.
4. Пайплайн исполнения
Полный путь от DSL до живого React-приложения выглядит так:
DSL → @lowcode/dsl-compiler → TSX-бандл
→ @lowcode/runtime-core.compileBundleToJsModules → JS CommonJS-модули
→ @lowcode/runtime-core.executeBundle → AppComponent
→ @lowcode/runtime-core.createRuntimeInstance → RuntimeInstance.RootComponent
4.1. compileBundleToJsModules
- Принимает
GeneratedBundleLike— набор файловfilename + content(TSX/JSX/TS/JS); - компилирует их через
@babel/standaloneс пресетамиreact,typescriptи плагиномtransform-modules-commonjs; - возвращает in-memory карту CommonJS-модулей.
4.2. moduleLoader
- Эмулирует
require+module.exports; - разрешает только относительные импорты;
- мапит импорт
reactна реальный React; - делает доступными:
- все компоненты из
RuntimeContext.componentsкак top-level имена; - объект
actionsизRuntimeContext.actionsкак top-level переменнуюactions.
- все компоненты из
4.3. executeBundle
- Создаёт loader над скомпилированными модулями;
- загружает модуль
App.tsx(по соглашению); - ищет экспорт
Appилиdefault; - возвращает
AppComponentилиnullпри ошибке.
4.4. createRuntimeInstance
-
компилирует бандл в JS-модули;
-
исполняет бандл и получает
AppComponent; -
создаёт
InternalRuntimeState:snapshot+initialSnapshot;listenersдля подписок;dataSourcesRegistryиdataSourceExecutor(если переданы);
-
формирует
RuntimeContext(components + actions); -
создаёт
RootComponent, который оборачиваетAppComponentвRuntimeRootи пробрасывает в него пропсы хоста; -
возвращает
RuntimeInstanceсRootComponent,getSnapshot,subscribe,dispatch.
5. Источники данных и RuntimeDataSourceExecutor
5.1. Интерфейс executor’а
Runtime-core не знает, как выполнять HTTP/DB/WebSocket — он лишь делегирует выполнение во внешний executor.
Контракт:
export interface RuntimeDataSourceExecutor {
execute(options: {
snapshot: RuntimeSnapshot;
action: CallDataSourceAction;
dataSource: DataSource;
}): Promise<unknown>;
}
Где:
snapshot— актуальное состояние на момент вызова (можно использоватьactivePageId, state и т.п.);action— DSL-экшнcallDataSource(восстановлен из команды);dataSource— описание источника данных из DSL (kind,config,mock).
5.2. Поведение applyRuntimeCommand для DataSources
Команда callDataSource:
-
Обновляет
snapshot.dataSources[dataSourceId]:- проставляет
__lastCallAtи__lastPayload; - если payload содержит
mockScenario, фиксирует его в__mockScenario, чтобы devtools понимали, какой сценарий принудительно запрошен;
- проставляет
-
Генерирует
RuntimeChangeReasonсkind: 'dataSourceCallStarted'; -
Если указан
dataSourceExecutorи он знает этотdataSourceId:- вызывает
executor.execute({ snapshot: nextSnapshot, action, dataSource }); - по
Promise:- при успехе объединяет результат с meta-полями и обновляет
snapshot.dataSources, а также записывает__lastResultAt; - при ошибке проставляет
__lastErrorAtи__lastError, не меняя page state;
- при успехе объединяет результат с meta-полями и обновляет
- в обоих случаях эмитится
RuntimeChangeReasonсkind: 'dataSourceChanged';
- вызывает
-
Если у
CallDataSourceActionестьassignResultToStateKeyи естьactivePageId, результат успешного вызова:- раскладывается по пути, либо пишется под ключ
assignResultToStateKeyвpageState[activePageId]; - при ошибке executor’а страничное состояние остаётся прежним, а информация о сбое доступна через
__lastError.
- раскладывается по пути, либо пишется под ключ
Команда refetchDataSource:
- обновляет
__lastRefetchAtдля нужногоdataSourceId; - генерирует
RuntimeChangeReasonсkind: 'dataSourceChanged'.
5.3. createDefaultDataSourceExecutor
В runtime/defaultDataSourceExecutor.ts находится базовая реализация executor’а, ориентированная на preview в builder-web.
Особенности:
-
static dataSource без мока → всегда возвращает
config.data; -
rest / другие виды без
mock.enabled === true→ бросают осмысленную ошибку вида:REST data source "UsersAPI" (id=users-ds) has no mock and cannot be executed in preview. In production runtime-host is expected to perform HTTP request: ... To use this data source in builder preview, configure a mock in the Data Sources panel.
-
поддерживает
mockв DSL (DataSource.mock), у которого теперь три блока:successMock— позитивный сценарий:mode: 'static'→ возвращаетvalue(илиconfig.dataдля static-источников, еслиvalueне задан) и учитываетdelayMs;mode: 'sequence'→ циклически возвращает элементыsequenceпо каждомуdataSourceId, пустой массив даётnull, аsequenceValueTypeуправляет автоматическим парсингом строк в JSON (json) либо сохранением текста (text);
errorScenarios— негативные сценарии, разделённые по типу:api— имитирует ответ сервера с произвольнымpayload,statusCodeиdelayMs;network— симулирует сетевую ошибку сmessage,statusCode/statusTextи задержкой;activeKind— какой из типов ошибок будет выбран по умолчанию приactiveScenario: 'error';
activeScenarioподсказывает, какой блок (success/error) использовать по умолчанию. Runtime сохраняет факт ручного переключения вsnapshot.dataSources[dsId].__mockScenario.
-
CallDataSourceAction.mockScenarioOverrideпозволяет переключить сценарий «на один вызов» (например, из RuntimeStateInspector). Executor также прикрепляет метаданные к ошибкам черезattachMockErrorResult, чтобыruntimeStateмог заполнить__errorPayload,__errorStatusCode/Textи дать возможностьassignErrorToStateKeyзаписать данные в state.
Важный момент: createDefaultDataSourceExecutor не выполняет реальных HTTP-запросов. Для production runtime-host ожидается отдельный executor, который будет реализовывать настоящие REST-вызовы.
6. Devtools и подписки на события
runtime-core предоставляет API для devtools:
-
formatRuntimeChangeReason(reason)— человеко-читаемое англоязычное описание причины изменения snapshot’а; -
type-guards по видам причин:
isNavigationChange(reason);isStateChangedReason(reason);isDataSourceChangedReason(reason);isCustomChangeReason(reason);
-
subscribeToRuntimeEvents(runtime, options)— обёртка надruntime.subscribe, которая позволяет:- слушать только интересующие
kinds; - дополнительно фильтровать события через
predicate; - получать (snapshot, reason) только для нужных событий.
- слушать только интересующие
Пример:
const unsubscribe = subscribeToRuntimeEvents(runtimeInstance, {
kinds: ['navigation', 'dataSourceChanged'],
predicate: (reason) => reason.kind === 'dataSourceChanged' && reason.dataSourceId === 'ds-users',
listener: (snapshot, reason) => {
console.log(formatRuntimeChangeReason(reason));
},
});
В связке с React-хуками (useRuntimeSnapshot, useDataSource, и т.д.) этого достаточно, чтобы собрать собственную панель devtools: лог событий, инспектор состояния, просмотр dataSources.
7. Использование в builder-web (preview)
В превью builder-web (PreviewPanel) runtime-core используется так:
-
Валидация DSL (структура + выражения).
-
Компиляция DSL → TSX-бандл через
@lowcode/dsl-compiler. -
Построение начального состояния:
initialAppStateизapp.appState;initialPageStateдля активной страницы изpage.state;initialDataSourcesState(по умолчанию пустые записи или прединициализация).
-
Создание runtime-инстанса:
const runtimeInstance = createRuntimeInstance({
bundle,
components: runtimeComponents,
actions: runtimeActions,
initialState: {
globalState: initialAppState,
pageState: activePageId ? { [activePageId]: initialPageState } : {},
activePageId,
dataSources: initialDataSourcesState,
},
dataSources: app.dataSources ?? [],
dataSourceExecutor: createDefaultDataSourceExecutor(),
onError: (err) => {
console.error('[PreviewPanel] Runtime error:', err);
},
}); -
Монтаж
runtimeInstance.RootComponentв панель превью. -
Использование:
useRuntimeSnapshotи других хуков внутри дерева превью (например, вRuntimeStateInspector);subscribeToRuntimeEventsсformatRuntimeChangeReasonдля панели Runtime events.
Так превью ведёт себя как минимальный runtime-host, полностью основанный на @lowcode/runtime-core.
8. Использование в runtime-host SPA
Отдельное приложение runtime-host SPA - тонкая обёртка над runtime-core.
Сценарий работы:
-
Получить от backend’а DSL или уже скомпилированный бандл.
-
Прогнать DSL через
@lowcode/dsl-compiler. -
Реестр компонентов и actions на базе
@lowcode/runtime-core: -
Создать
RuntimeInstanceчерезcreateRuntimeInstanceи смонтироватьRootComponentв корень SPA. -
Подключить devtools-панель поверх runtime-core (state/events/dataSources).
Вся тяжёлая логика исполнения (команды, snapshot, dataSources, devtools) уже реализована в runtime-core и переиспользуется между builder-web и runtime-host.
9. Тестирование
Текущая структура тестов для runtime-core:
-
core-слой — тесты компиляции и загрузки:
compileBundleToJsModules(обработка TSX/JSX/TS/JS);moduleLoader(относительные импорты, кеширование, виртуальныйreact);executeBundle(поискApp/default, обработка ошибок).
-
state/runtime-слой — тесты работы со snapshot’ом:
- инициализация начального состояния по
initialState; - обработка команд (
navigate,setState,callDataSource,refetchDataSource,reset); - поведение при ошибках executor’а (запись
__lastError).
- инициализация начального состояния по
-
DataSources — тесты
defaultDataSourceExecutor:- static dataSource без моков →
config.data; - rest без моков → осмысленная ошибка с подсказкой про runtime-host и моки;
- static dataSource без моков →
-
successMock(static/sequence) иerrorScenarios(api/network сactiveKind) работают независимо, учитываяdelayMsиsequenceValueType;- корректное поведение при
mock.enabled === false.
- корректное поведение при
-
React-оболочка — тесты
createReactRuntimeInstanceиRuntimeRoot:- корректный проброс пропсов в
AppComponent; - наличие
RuntimeReactContextв дереве; - интеграция
dispatchи подписок.
- корректный проброс пропсов в
-
devtools — тесты
runtimeDevtools:formatRuntimeChangeReasonдля всех стандартныхkind;- корректность type-guards;
- фильтрация событий в
subscribeToRuntimeEvents.
Запуск тестов (через Jest в монорепо):
pnpm --filter @lowcode/runtime-core test
10. Attachments и универсальные компоненты
10.1. Утилита createAttachmentImage
Runtime-core экспортирует универсальную фабрику для создания Image компонента с поддержкой загрузки attachments:
// packages/runtime-core/src/components/AttachmentImage.tsx
export function createAttachmentImage(options: {
fetchAttachment: (attachmentId: string) => Promise<AttachmentInfo>;
cacheTtlMs?: number;
}): FC<AttachmentImageProps>
Особенности:
- Фабричный паттерн: принимает
fetchAttachmentот хоста (builder-web, runtime-host) - Автоматическое определение attachment ID:
- Если
sourceType === 'upload'→srcсчитается attachment ID - (Legacy) Если
srcначинается сatt-→srcсчитается attachment ID
- Если
- In-memory кеширование URL с TTL (по умолчанию 5 минут)
- Состояния loading и error с визуальной индикацией
- Работа с обычными URL как простой
<img>
Использование в builder-web:
// apps/builder-web/src/components/PreviewPanel/previewComponents.tsx
import { createAttachmentImage } from '@lowcode/runtime-core';
import { getAttachment } from '../../api/client';
const Image = createAttachmentImage({
fetchAttachment: getAttachment,
});
export const previewComponents = {
...defaultDevComponents,
Image,
};
Использование в runtime-host:
// apps/runtime-host/src/components/runtimeComponents.tsx
import { createAttachmentImage } from '@lowcode/runtime-core';
import { fetchAttachment } from '../api/client';
const Image = createAttachmentImage({
fetchAttachment,
});
export const runtimeHostComponents = {
...defaultDevComponents,
Image,
};
10.1.1. Утилита createAttachmentVideo
Runtime-core также экспортирует фабрику для создания Video компонента с поддержкой загрузки attachments:
// packages/runtime-core/src/components/AttachmentVideo.tsx
export function createAttachmentVideo(options: {
fetchAttachment: (attachmentId: string) => Promise<AttachmentInfo>;
cacheTtlMs?: number;
}): FC<AttachmentVideoProps>
Особенности:
- Полностью аналогична
createAttachmentImage - Использует те же типы
AttachmentInfoиFetchAttachmentFn - Поддерживает дополнительные пропсы:
controls,autoplay,loop,muted,poster - Те же механизмы автоопределения attachment ID и кеширования
Использование в runtime-host:
// apps/runtime-host/src/components/runtimeComponents.tsx
import { createAttachmentVideo, createAttachmentImage } from '@lowcode/runtime-core';
import { fetchAttachment } from '../api/client';
const Image = createAttachmentImage({ fetchAttachment });
const Video = createAttachmentVideo({ fetchAttachment });
export const runtimeHostComponents = {
...defaultDevComponents,
Image,
Video,
};
10.2. Зачем нужна фабрика?
Фабричный подход позволяет:
- Избежать дублирования кода загрузки и кеширования
- Унифицировать поведение между builder-web preview и runtime-host
- Гибкость: каждый хост предоставляет свою реализацию API
- Переиспользование: легко добавить поддержку в новый хост
Без фабрики пришлось бы дублировать ~150 строк логики в каждом хосте.
10.3. Процесс загрузки attachment
1. DSL: <Image src="att-uuid" sourceType="upload" />
или: <Video src="att-uuid" sourceType="upload" />
2. Runtime рендерит компонент Image/Video
- Видит sourceType === 'upload'
- Проверяет кеш (TTL 5 мин)
3. Если не в кеше:
- Вызывает options.fetchAttachment('att-uuid')
- Хост делает HTTP GET /attachments/att-uuid
- Backend генерирует presigned URL (TTL 1 час)
- Возвращает { id, downloadUrl, ... }
4. Image/Video компонент:
- Сохраняет downloadUrl в кеш
- Отображает <img src={downloadUrl} /> или <video src={downloadUrl} />
5. При следующем рендере:
- Берет URL из кеша (быстро)
- Через 5 минут кеш истекает → повторный запрос
10.4. Критические исправления
При интеграции attachments были исправлены три критические проблемы:
10.4.1. Определение attachment ID по sourceType
Проблема: Изначальная реализация определяла attachment ID только по префиксу att-:
if (src && src.startsWith('att-')) {
// load as attachment
}
Это не работало, потому что реальные attachment ID — это UUIDs вида 550e8400-...
Решение (packages/runtime-core/src/components/AttachmentImage.tsx:104-105):
const isAttachmentId =
(sourceType === 'upload' && !!src) || // primary method - check sourceType
(typeof src === 'string' && src.startsWith('att-')); // legacy support
if (!src || !isAttachmentId) {
setImageUrl(src ?? ''); // normal URL
return;
}
// Загружаем attachment по ID
const attachment = await fetchAttachment(src);
Теперь проверка sourceType === 'upload' является основным способом определения attachment ID.
10.4.2. Другие исправления в других модулях
Для полной работы системы также были исправлены:
-
Backend валидация projectId (см. API документацию):
- Добавлен
ensureProject()для валидации и возврата 404/403 вместо 500
- Добавлен
-
Frontend использование правильного projectId (см. Builder-web документацию):
- PropertiesPanel теперь использует UUID из
useEditorState(), а неapp.id
- PropertiesPanel теперь использует UUID из
10.5. Резюме attachments в runtime-core
Система attachments в runtime-core построена на принципе инверсии зависимостей:
- Runtime-core предоставляет универсальные фабрики
createAttachmentImageиcreateAttachmentVideo - Каждый хост (builder-web, runtime-host) предоставляет свою реализацию
fetchAttachment - Вся логика загрузки, кеширования и отображения унифицирована
- Нет дублирования кода между хостами
- Поддерживаются как изображения, так и видео с одинаковым API
Подробная документация backend модуля: API / Модуль Attachments
10.6. Общее резюме
@lowcode/runtime-core — это единый движок выполнения DSL-приложений, который:
- принимает сгенерированный TSX/JSX-бандл от
@lowcode/dsl-compiler; - компилирует и исполняет его в контролируемом окружении;
- управляет runtime-состоянием (global/page/dataSources/activePageId);
- обрабатывает типизированные команды (
navigate,setState,callDataSource,refetchDataSource,reset,custom); - делегирует работу с источниками данных в
RuntimeDataSourceExecutor(с дефолтной реализацией через моки); - предоставляет удобный контракт для хоста (
RuntimeInstance); - даёт devtools-хелперы и хуки для инспекции состояния и событий;
- экспортирует универсальные утилиты для создания компонентов с поддержкой attachments;
- переиспользуется в превью builder-web и в runtime-host SPA.
Всё, что связано с UI, backend'ом и конкретным окружением, живёт снаружи runtime-core. Здесь находятся только общие абстракции и алгоритмы, обеспечивающие предсказуемое поведение DSL-приложений во всех средах выполнения.