Skip to main content

@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.idcallDataSource. Важно: режим 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:

  1. Обновляет snapshot.dataSources[dataSourceId]:

    • проставляет __lastCallAt и __lastPayload;
    • если payload содержит mockScenario, фиксирует его в __mockScenario, чтобы devtools понимали, какой сценарий принудительно запрошен;
  2. Генерирует RuntimeChangeReason с kind: 'dataSourceCallStarted';

  3. Если указан dataSourceExecutor и он знает этот dataSourceId:

    • вызывает executor.execute({ snapshot: nextSnapshot, action, dataSource });
    • по Promise:
      • при успехе объединяет результат с meta-полями и обновляет snapshot.dataSources, а также записывает __lastResultAt;
      • при ошибке проставляет __lastErrorAt и __lastError, не меняя page state;
    • в обоих случаях эмитится RuntimeChangeReason с kind: 'dataSourceChanged';
  4. Если у 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 используется так:

  1. Валидация DSL (структура + выражения).

  2. Компиляция DSL → TSX-бандл через @lowcode/dsl-compiler.

  3. Построение начального состояния:

    • initialAppState из app.appState;
    • initialPageState для активной страницы из page.state;
    • initialDataSourcesState (по умолчанию пустые записи или прединициализация).
  4. Создание 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);
    },
    });
  5. Монтаж runtimeInstance.RootComponent в панель превью.

  6. Использование:

    • useRuntimeSnapshot и других хуков внутри дерева превью (например, в RuntimeStateInspector);
    • subscribeToRuntimeEvents с formatRuntimeChangeReason для панели Runtime events.

Так превью ведёт себя как минимальный runtime-host, полностью основанный на @lowcode/runtime-core.


8. Использование в runtime-host SPA

Отдельное приложение runtime-host SPA - тонкая обёртка над runtime-core.

Сценарий работы:

  1. Получить от backend’а DSL или уже скомпилированный бандл.

  2. Прогнать DSL через @lowcode/dsl-compiler.

  3. Реестр компонентов и actions на базе @lowcode/runtime-core:

  4. Создать RuntimeInstance через createRuntimeInstance и смонтировать RootComponent в корень SPA.

  5. Подключить 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 и моки;
  • 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. Зачем нужна фабрика?

Фабричный подход позволяет:

  1. Избежать дублирования кода загрузки и кеширования
  2. Унифицировать поведение между builder-web preview и runtime-host
  3. Гибкость: каждый хост предоставляет свою реализацию API
  4. Переиспользование: легко добавить поддержку в новый хост

Без фабрики пришлось бы дублировать ~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. Другие исправления в других модулях

Для полной работы системы также были исправлены:

  1. Backend валидация projectId (см. API документацию):

    • Добавлен ensureProject() для валидации и возврата 404/403 вместо 500
  2. Frontend использование правильного projectId (см. Builder-web документацию):

    • PropertiesPanel теперь использует UUID из useEditorState(), а не app.id

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-приложений во всех средах выполнения.