Net
@virentia/net-core описывает удалённые данные как модель: какой fetch выполняется, когда, как ведут себя пересекающиеся запуски и как восстанавливаются ошибки — всё вне UI.
Главная мысль: query — это эффект. Это настоящий эффект @virentia/core, поэтому у него уже есть pending, doneData, failData, abort, персональный signal на вызов и изоляция по scope. Net добавляет сверху два стора — data и stale — и небольшой набор композируемых операторов.
pnpm add @virentia/net-core @virentia/coreimport { concurrency, query, retry, trigger } from "@virentia/net-core";
const userQuery = query({
handler: async ({ id }: { id: string }, { signal }) => {
const res = await fetch(`/api/users/${id}`, { signal });
return (await res.json()) as User;
},
use: [concurrency({ strategy: "takeLatest" }), retry({ times: 3, delay: 300 })],
});
userQuery.pending; // Store<boolean>
userQuery.data; // Store<User | null>
userQuery.failData; // Event<Error>
trigger(userQuery, { on: userRoute.opened, params: () => ({ id: userRoute.params.value.id }) });Из чего состоит
query— fetch-эффект плюс сторыdata/staleиrefresh/reset.mutation— сторона записи: тот же эффект плюсoptimisticиinvalidates.trigger— запуск query/mutation, когда срабатывает любой юнит Virentia.- операторы —
concurrencyиretryкак middleware вuse: []. - адаптеры — подложить под query TanStack Query или Apollo, не меняя её поверхность.
- дефолты —
overrideDefaultsдля общего executor'а или операторов, глобально или per-scope.
Где живёт состояние
data и pending у query — per-scope, как любой стор Virentia: читайте их через useUnit в компонентах или scoped(scope, () => userQuery.data.value) в обычном коде, а в тестах гоняйте через scoped(scope, () => userQuery(payload)). Состояние операторов — in-flight запуск takeLatest, дорожки по ключу — тоже ключуется по scope, поэтому тесты и SSR-запросы не протекают друг в друга.
cache() и барьеры в планах; всё выше уже есть.