Skip to content

QueryRuntime

QueryRuntime — инфраструктурный корень Query Layer. Он не выполняет конкретный endpoint сам. Runtime создаёт общий QueryClient, общий executor запросов, управляет browser lifecycle и выдаёт ограниченные participant scopes, через которые создаются MobX stores.

Создание: createQueryRuntime(...).

Когда runtime не нужен

Для одной формы или одного изолированного экрана можно создать RequestStore, FetchStore или другой store напрямую. Это проще.

Когда runtime нужен

  • несколько feature должны использовать общий query cache;
  • нужны общие настройки retry, stale time и refetch on focus;
  • нужно централизованно освободить все запросы;
  • подключаются persistence, cross-tab sync или offline-инфраструктура;
  • host и remote в Module Federation разделяют server state;
  • нужны participant permissions и диагностика ресурсов.

Полный lifecycle приложения

ts
import { createQueryRuntime, type IScopedQueryRuntime } from "@dubium/query-layer/runtime"

// Runtime создаётся один раз на экземпляр приложения.
export const runtime = createQueryRuntime({
    // Режим описывает назначение экземпляра.
    mode: "application",

    // applicationId отделяет cache этого приложения от других runtime.
    identity: {
        applicationId: "customer-portal",
        version: "1.0.0",
    },

    // Не более пяти одновременных HTTP-попыток всех Store этого runtime.
    maxConcurrentRequests: 5,

    // Общая политика для всех query, созданных через scope.
    queryClientConfig: {
        defaultOptions: {
            queries: {
                staleTime: 30_000,
                retry: 1,
            },
            mutations: {
                retry: false,
            },
        },
        refetchOnReconnect: true,
        refetchOnWindowFocus: true,
    },
})

// initialize подключает focus/online lifecycle QueryClient.
// Вызывайте его один раз до render React-root.
await runtime.initialize()

// Scope представляет конкретный mount приложения.
// Для participantType application явные namespace permissions не обязательны.
export const appScope: IScopedQueryRuntime = runtime.createParticipantScope({
    participantId: "customer-portal-root",
    participantType: "application",
    version: "1.0.0",
})

В обычном приложении можно создать один appScope. В Module Federation host создаёт отдельный scope для каждого mount remote. Это не глобальная переменная: host явно передаёт scope в функцию remote.mount(...).

Domain store через scope

ts
import { makeAutoObservable } from "mobx"

import type { INormalizedAxiosError } from "@dubium/query-layer"
import type { IScopedQueryRuntime } from "@dubium/query-layer/runtime"

import { getMyProfileApi, type Profile } from "../api/profile.api"

export class ProfileStore {
    // Handler будет создан на общем client/executor runtime.
    private readonly requestHandler

    constructor(scope: IScopedQueryRuntime) {
        this.requestHandler = scope.request.createStore({
            api: getMyProfileApi,
            options: {
                retry: 1,
            },
        })

        makeAutoObservable<this, "requestHandler">(
            this,
            {
                requestHandler: false,
            },
            {
                autoBind: true,
            },
        )
    }

    get profile(): Profile | null {
        return this.requestHandler.data ?? null
    }

    get loading(): boolean {
        return this.requestHandler.loading
    }

    get error(): INormalizedAxiosError | null {
        return this.requestHandler.error
    }

    async fetch(): Promise<Profile | null> {
        // API-функция не принимает values, поэтому execute вызывается без аргументов.
        const response = await this.requestHandler.execute()
        return response?.data ?? null
    }

    dispose(): void {
        this.requestHandler.dispose()
    }
}

Scope имеет четыре factory-фасада:

ts
scope.request.createStore(...)       // RequestStore
scope.fetch.createStore(...)         // FetchStore
scope.query.createStore(...)         // QueryStore
scope.query.createInfiniteStore(...) // InfiniteQueryStore
scope.mutation.createStore(...)      // MutationStore

Эти stores автоматически используют общий client, получают owner context и освобождаются при scope.dispose().

Завершение приложения

ts
// Сначала прекращаем работу конкретного participant.
appScope.dispose()

// Затем отключаем runtime и его browser listeners.
await runtime.dispose()

dispose() идемпотентен: повторный вызов безопасен. После dispose нельзя создавать новые scopes или повторно инициализировать этот экземпляр.

Конфигурация QueryRuntime

ПолеTypeDefaultЧто делает
identity{ applicationId: string; version?: string }Обязательная идентичность runtime. applicationId не может быть пустым.
maxConcurrentRequestsnumberбез ограниченияЛимит одновременных HTTP-попыток общего пула runtime.
requestConcurrency{ default?: number; groups?: Record<string, number> }без ограниченияОбщий лимит и независимые лимиты именованных requestGroup.
mode"application" | "host" | "standalone-remote"application по смыслуОписывает сценарий использования. Доступ к данным фактически ограничивается participant registration.
capabilitiesTQueryRuntimeCapability[]fetch, query, mutation, request и session, если настроена сессияВозможности, объявляемые при MF compatibility handshake.
protocolVersionnumberтекущая версия протоколаВерсия контракта host/remote. Должна быть положительным целым числом.
queryClientConfigIQueryClientConfigdefaults QueryClientОбщие query/mutation options, GC, focus и reconnect policy.
requestExecutorOptionsIRequestExecutorOptionsdefaults executorLifecycle events, clock и timeout manager общего executor.
sessionCoordinatorISessionCoordinatorContractГотовый внешний coordinator авторизации.
sessionCoordinatorOptionsISessionCoordinatorOptionsНастройки coordinator, которым будет владеть runtime. Нельзя передавать вместе с sessionCoordinator.
idGeneratorIIdGeneratorRuntimeIdGeneratorГенератор instance, operation и correlation ids. Полезен в тестах.
retryRandom() => numberMath.randomУправляемый random для детерминированных тестов встроенного retry jitter.

Подробная семантика очереди, групп и повторных попыток описана в разделе «Конкурентность запросов и retry jitter».

queryClientConfig

ПолеTypeЧто делает
defaultOptions.queriesOmit<Partial<IQueryOptions>, "queryFn" | "queryKey">Общие staleTime, gcTime, retry, networkMode и другие query defaults.
defaultOptions.mutations{ retry?, retryDelay?, networkMode? }Общая mutation policy.
refetchOnReconnectbooleanОбновлять активные stale query после восстановления сети.
refetchOnWindowFocusbooleanОбновлять активные stale query при возвращении фокуса.
loggerIQueryLayerLoggerПолучатель внутренних диагностических событий.
clockIClockВнедряемый источник времени. Обычно нужен тестам.
timeoutManagerITimeoutManagerВнедряемые таймеры. Обычно нужны тестам.

Сборка неактивного cache выполняется per-query по gcTime; отдельного глобального gcInterval в публичной конфигурации нет.

Runtime capabilities

Это не HTTP-методы и не права пользователя. Это список частей runtime API, которые host обещает remote:

ЗначениеЧто host предоставляет
requestСоздание RequestStore через scope.request.
fetchСоздание FetchStore через scope.fetch.
queryQueryStore, InfiniteQueryStore, чтение cache и invalidation через scope.query.
mutationСоздание MutationStore через scope.mutation.
sessionОбщий SessionCoordinator настроен в host runtime.

Remote перечисляет только обязательные значения в requiredRuntimeCapabilities. Если host их не объявил, registration завершится ошибкой до создания scope.

Методы QueryRuntime

Метод/полеTypeЧто делает
initialize()Promise<void>Подключает QueryClient к browser focus/online lifecycle и инициализирует owned session coordinator.
createParticipantScope(registration)IScopedQueryRuntimeРегистрирует application/host/remote mount и возвращает ограниченные store factories.
getProtocolDescriptor()IQueryRuntimeProtocolDescriptorВозвращает application id, version, protocol и capabilities для handshake.
getDiagnostics()IQueryRuntimeDiagnosticsВозвращает только безопасные счётчики без payload и credentials.
dispose()Promise<void>Освобождает scopes, requests, session и QueryClient.
activeParticipantCountnumberТекущее число живых scopes.
identityIRuntimeIdentityИдентичность приложения.
protocolVersionnumberВерсия протокола scope.
sessionCoordinatorISessionCoordinatorContract | undefinedОбщий coordinator, если был настроен.

Participant registration

ПолеTypeЧто означает
participantIdstringСтабильное имя модуля, например billing-remote.
participantTypeapplication | host | remoteРоль конкретного mount.
versionstringВерсия participant.
instanceIdstringУникальный id mount. Обычно генерируется runtime.
protocolVersionnumberТребуемая версия протокола.
minimumRuntimeVersionstringМинимальная совместимая версия host runtime.
requiredRuntimeCapabilitiesTQueryRuntimeCapability[]Возможности, без которых participant не работает.
tenantId, userIdstringРаздел данных текущей сессии.
capabilitiesIParticipantCapabilitiesРазрешённые query/mutation namespaces и global invalidation. Для remote обязательны.

Для обычного приложения достаточно одного простого application participant. Все детали remote permissions, передачи scope и разделения кэша находятся в разделе Module Federation.

localStorage и runtime

Runtime сам ничего не записывает в localStorage. Сохранение включается отдельным persistence plugin и только для query с meta.persist === true. Подробнее: Persistence и localStorage в Module Federation.