@fozy-labs/rx-toolkit 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +22 -13
- package/dist/common/devtools/reduxDevtools.js +17 -2
- package/dist/common/react/index.d.ts +1 -0
- package/dist/common/react/index.js +1 -0
- package/dist/common/react/useIsomorphicLayoutEffect.d.ts +17 -0
- package/dist/common/react/useIsomorphicLayoutEffect.js +17 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/query/api/createApi.d.ts +4 -0
- package/dist/query/api/createApi.js +9 -0
- package/dist/query/api/index.d.ts +1 -0
- package/dist/query/api/index.js +1 -0
- package/dist/query/constants.d.ts +12 -0
- package/dist/query/constants.js +15 -0
- package/dist/query/core/api/Api.d.ts +20 -0
- package/dist/query/core/api/Api.js +129 -0
- package/dist/query/core/api/constants.d.ts +2 -0
- package/dist/query/core/api/constants.js +3 -0
- package/dist/query/core/api/index.d.ts +4 -0
- package/dist/query/core/api/index.js +4 -0
- package/dist/query/core/api/mergeHooks.d.ts +2 -0
- package/dist/query/core/api/mergeHooks.js +26 -0
- package/dist/query/core/api/normalizeLinks.d.ts +2 -0
- package/dist/query/core/api/normalizeLinks.js +11 -0
- package/dist/query/core/cache/CacheEntry.d.ts +21 -0
- package/dist/query/core/cache/CacheEntry.js +54 -0
- package/dist/query/core/cache/CacheMap.d.ts +19 -0
- package/dist/query/core/cache/CacheMap.js +32 -0
- package/dist/query/core/cache/QueryCacheEntry.d.ts +21 -0
- package/dist/query/core/cache/QueryCacheEntry.js +136 -0
- package/dist/query/core/cache/index.d.ts +3 -0
- package/dist/query/core/cache/index.js +3 -0
- package/dist/query/core/command/Command.d.ts +67 -0
- package/dist/query/core/command/Command.js +253 -0
- package/dist/query/core/command/CommandAgent.d.ts +17 -0
- package/dist/query/core/command/CommandAgent.js +67 -0
- package/dist/query/core/command/LinkManager.d.ts +24 -0
- package/dist/query/core/command/LinkManager.js +71 -0
- package/dist/query/core/command/index.d.ts +2 -0
- package/dist/query/core/command/index.js +2 -0
- package/dist/query/core/errors/CacheEntryRemovedError.d.ts +7 -0
- package/dist/query/core/errors/CacheEntryRemovedError.js +9 -0
- package/dist/query/core/errors/MachineStateError.d.ts +8 -0
- package/dist/query/core/errors/MachineStateError.js +10 -0
- package/dist/query/core/errors/MachineTransitionError.d.ts +7 -0
- package/dist/query/core/errors/MachineTransitionError.js +9 -0
- package/dist/query/core/errors/index.d.ts +3 -0
- package/dist/query/core/errors/index.js +3 -0
- package/dist/query/core/index.d.ts +8 -0
- package/dist/query/core/index.js +8 -0
- package/dist/query/core/machine/Machine.d.ts +21 -0
- package/dist/query/core/machine/Machine.js +42 -0
- package/dist/query/core/machine/MachineBase.d.ts +31 -0
- package/dist/query/core/machine/MachineBase.js +176 -0
- package/dist/query/core/machine/MachineError.d.ts +10 -0
- package/dist/query/core/machine/MachineError.js +19 -0
- package/dist/query/core/machine/MachinePending.d.ts +13 -0
- package/dist/query/core/machine/MachinePending.js +32 -0
- package/dist/query/core/machine/MachineRefreshError.d.ts +12 -0
- package/dist/query/core/machine/MachineRefreshError.js +23 -0
- package/dist/query/core/machine/MachineRefreshing.d.ts +15 -0
- package/dist/query/core/machine/MachineRefreshing.js +43 -0
- package/dist/query/core/machine/MachineSuccess.d.ts +12 -0
- package/dist/query/core/machine/MachineSuccess.js +23 -0
- package/dist/query/core/machine/MachineWithData.d.ts +25 -0
- package/dist/query/core/machine/MachineWithData.js +73 -0
- package/dist/query/core/machine/index.d.ts +9 -0
- package/dist/query/core/machine/index.js +9 -0
- package/dist/query/core/machine/machine-helpers.d.ts +9 -0
- package/dist/query/core/machine/machine-helpers.js +80 -0
- package/dist/query/core/patcher/Patcher.d.ts +30 -0
- package/dist/query/core/patcher/Patcher.js +122 -0
- package/dist/query/core/patcher/index.d.ts +1 -0
- package/dist/query/core/patcher/index.js +1 -0
- package/dist/query/core/resource/Resource.d.ts +105 -0
- package/dist/query/core/resource/Resource.js +340 -0
- package/dist/query/core/resource/ResourceAgent.d.ts +37 -0
- package/dist/query/core/resource/ResourceAgent.js +179 -0
- package/dist/query/core/resource/index.d.ts +2 -0
- package/dist/query/core/resource/index.js +2 -0
- package/dist/query/core/snapshoter/Snapshoter.d.ts +22 -0
- package/dist/query/core/snapshoter/Snapshoter.js +78 -0
- package/dist/query/core/snapshoter/index.d.ts +2 -0
- package/dist/query/core/snapshoter/index.js +1 -0
- package/dist/query/core/syncer/Syncer.d.ts +32 -0
- package/dist/query/core/syncer/Syncer.js +85 -0
- package/dist/query/core/syncer/index.d.ts +2 -0
- package/dist/query/core/syncer/index.js +1 -0
- package/dist/query/index.d.ts +5 -10
- package/dist/query/index.js +5 -13
- package/dist/query/lib/broadcastSyncDriver.d.ts +5 -0
- package/dist/query/lib/broadcastSyncDriver.js +46 -0
- package/dist/query/lib/index.d.ts +3 -0
- package/dist/query/lib/index.js +3 -0
- package/dist/{query-v2 → query}/lib/stableStringify.js +5 -3
- package/dist/query/lib/toKeyed.d.ts +11 -0
- package/dist/query/lib/toKeyed.js +18 -0
- package/dist/query/react/ReactHooksPlugin.d.ts +31 -0
- package/dist/query/react/ReactHooksPlugin.js +21 -0
- package/dist/query/react/index.d.ts +3 -0
- package/dist/query/react/index.js +3 -0
- package/dist/query/react/useCommand.d.ts +2 -0
- package/dist/query/react/useCommand.js +14 -0
- package/dist/query/react/useResource.d.ts +2 -0
- package/dist/query/react/useResource.js +16 -0
- package/dist/query/types/api.d.ts +39 -0
- package/dist/query/types/cache.d.ts +51 -0
- package/dist/query/types/command.d.ts +52 -0
- package/dist/query/types/common.d.ts +65 -0
- package/dist/query/types/index.d.ts +8 -4
- package/dist/query/types/index.js +8 -5
- package/dist/query/types/plugin-hkt.d.ts +64 -0
- package/dist/query/types/resource.d.ts +49 -0
- package/dist/query/types/snapshot.d.ts +27 -0
- package/dist/query/types/snapshot.js +2 -0
- package/dist/query/types/state.d.ts +24 -0
- package/dist/signals/base/ComputeCache.js +1 -1
- package/dist/signals/base/Devtools.js +2 -6
- package/dist/signals/signals/Computed.d.ts +1 -0
- package/dist/signals/signals/Computed.js +5 -1
- package/dist/signals/signals/Effect.d.ts +0 -4
- package/dist/signals/signals/Effect.js +0 -6
- package/dist/signals/signals/LocalState.d.ts +1 -10
- package/dist/signals/signals/LocalState.js +0 -12
- package/dist/signals/signals/Signal.d.ts +2 -8
- package/dist/signals/signals/Signal.js +1 -10
- package/dist/signals/signals/State.d.ts +2 -1
- package/dist/signals/signals/State.js +9 -0
- package/dist/signals/types/SignalOptions.d.ts +0 -2
- package/dist/signals/types/normalizeSignalOptions.js +0 -3
- package/dist/signals/types/signals.types.d.ts +3 -1
- package/docs/CHANGELOG.md +61 -31
- package/docs/migrations/0.6.0.md +224 -0
- package/docs/query/README.md +52 -562
- package/docs/query/api/README.md +59 -0
- package/docs/query/api/_CacheEntry.md +39 -0
- package/docs/query/api/_CacheMap.md +30 -0
- package/docs/query/api/_QueryCacheEntry.md +81 -0
- package/docs/query/api/command-agent.md +60 -0
- package/docs/query/api/command.md +76 -0
- package/docs/query/api/resource-agent.md +68 -0
- package/docs/query/api/resource.md +77 -0
- package/docs/query/concepts/agent.md +70 -0
- package/docs/query/concepts/architecture.md +139 -0
- package/docs/query/concepts/cache.md +81 -0
- package/docs/query/concepts/dataflows.md +473 -0
- package/docs/query/concepts/keyed.md +42 -0
- package/docs/query/concepts/machine.md +106 -0
- package/docs/query/concepts/patching.md +85 -0
- package/docs/query/usage/broadcast.md +188 -0
- package/docs/query/usage/command.md +203 -0
- package/docs/query/usage/lifecycle.md +114 -0
- package/docs/query/usage/links.md +125 -0
- package/docs/query/usage/plugins.md +96 -0
- package/docs/query/usage/resource.md +206 -0
- package/docs/query/usage/snapshot.md +80 -0
- package/docs/usage/react/README.md +45 -91
- package/package.json +6 -9
- package/dist/query/SKIP_TOKEN.d.ts +0 -1
- package/dist/query/SKIP_TOKEN.js +0 -1
- package/dist/query/api/createCommand.d.ts +0 -21
- package/dist/query/api/createCommand.js +0 -20
- package/dist/query/api/createOperation.d.ts +0 -5
- package/dist/query/api/createOperation.js +0 -6
- package/dist/query/api/createResource.d.ts +0 -3
- package/dist/query/api/createResource.js +0 -2
- package/dist/query/api/createResourceDuplicator.d.ts +0 -4
- package/dist/query/api/createResourceDuplicator.js +0 -2
- package/dist/query/api/resetAllQueriesCache.d.ts +0 -1
- package/dist/query/api/resetAllQueriesCache.js +0 -4
- package/dist/query/core/Command/Command.d.ts +0 -35
- package/dist/query/core/Command/Command.js +0 -210
- package/dist/query/core/Command/CommandAgent.d.ts +0 -19
- package/dist/query/core/Command/CommandAgent.js +0 -54
- package/dist/query/core/Command/index.d.ts +0 -2
- package/dist/query/core/Command/index.js +0 -2
- package/dist/query/core/Operation/Operation.d.ts +0 -8
- package/dist/query/core/Operation/Operation.js +0 -4
- package/dist/query/core/Operation/OperationAgent.d.ts +0 -4
- package/dist/query/core/Operation/OperationAgent.js +0 -4
- package/dist/query/core/QueriesCache.d.ts +0 -9
- package/dist/query/core/QueriesCache.js +0 -28
- package/dist/query/core/QueriesLifetimeHooks.d.ts +0 -22
- package/dist/query/core/QueriesLifetimeHooks.js +0 -86
- package/dist/query/core/ResetAllQueriesSignal.d.ts +0 -6
- package/dist/query/core/ResetAllQueriesSignal.js +0 -11
- package/dist/query/core/Resource/Resource.d.ts +0 -51
- package/dist/query/core/Resource/Resource.js +0 -232
- package/dist/query/core/Resource/ResourceAgent.d.ts +0 -35
- package/dist/query/core/Resource/ResourceAgent.js +0 -110
- package/dist/query/core/Resource/ResourceDuplicator.d.ts +0 -73
- package/dist/query/core/Resource/ResourceDuplicator.js +0 -227
- package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +0 -35
- package/dist/query/core/Resource/ResourceDuplicatorAgent.js +0 -110
- package/dist/query/core/Resource/ResourceRef.d.ts +0 -16
- package/dist/query/core/Resource/ResourceRef.js +0 -136
- package/dist/query/lib/IndirectMap.d.ts +0 -19
- package/dist/query/lib/IndirectMap.js +0 -88
- package/dist/query/lib/ReactiveCache.d.ts +0 -62
- package/dist/query/lib/ReactiveCache.js +0 -80
- package/dist/query/react/useCommandAgent.d.ts +0 -24
- package/dist/query/react/useCommandAgent.js +0 -39
- package/dist/query/react/useOperationAgent.d.ts +0 -6
- package/dist/query/react/useOperationAgent.js +0 -6
- package/dist/query/react/useResourceAgent.d.ts +0 -6
- package/dist/query/react/useResourceAgent.js +0 -31
- package/dist/query/react/useResourceRef.d.ts +0 -5
- package/dist/query/react/useResourceRef.js +0 -13
- package/dist/query/types/Command.types.d.ts +0 -154
- package/dist/query/types/Command.types.js +0 -1
- package/dist/query/types/Operation.types.d.ts +0 -13
- package/dist/query/types/Operation.types.js +0 -1
- package/dist/query/types/Resource.types.d.ts +0 -129
- package/dist/query/types/Resource.types.js +0 -1
- package/dist/query/types/shared.types.d.ts +0 -26
- package/dist/query/types/shared.types.js +0 -1
- package/dist/query-v2/api/createApi.d.ts +0 -10
- package/dist/query-v2/api/createApi.js +0 -83
- package/dist/query-v2/core/common/CacheEntry.d.ts +0 -29
- package/dist/query-v2/core/common/CacheEntry.js +0 -71
- package/dist/query-v2/core/common/CacheMap.d.ts +0 -38
- package/dist/query-v2/core/common/CacheMap.js +0 -127
- package/dist/query-v2/core/common/LifecycleHooks.d.ts +0 -22
- package/dist/query-v2/core/common/LifecycleHooks.js +0 -104
- package/dist/query-v2/core/common/index.d.ts +0 -3
- package/dist/query-v2/core/common/index.js +0 -3
- package/dist/query-v2/core/index.d.ts +0 -3
- package/dist/query-v2/core/index.js +0 -3
- package/dist/query-v2/core/machines/Machine.d.ts +0 -14
- package/dist/query-v2/core/machines/Machine.js +0 -33
- package/dist/query-v2/core/machines/MachineError.d.ts +0 -11
- package/dist/query-v2/core/machines/MachineError.js +0 -26
- package/dist/query-v2/core/machines/MachineIdle.d.ts +0 -8
- package/dist/query-v2/core/machines/MachineIdle.js +0 -19
- package/dist/query-v2/core/machines/MachinePending.d.ts +0 -12
- package/dist/query-v2/core/machines/MachinePending.js +0 -29
- package/dist/query-v2/core/machines/MachineRefreshing.d.ts +0 -14
- package/dist/query-v2/core/machines/MachineRefreshing.js +0 -46
- package/dist/query-v2/core/machines/MachineSuccess.d.ts +0 -16
- package/dist/query-v2/core/machines/MachineSuccess.js +0 -42
- package/dist/query-v2/core/machines/MachineWithData.d.ts +0 -18
- package/dist/query-v2/core/machines/MachineWithData.js +0 -40
- package/dist/query-v2/core/machines/Patcher.d.ts +0 -20
- package/dist/query-v2/core/machines/Patcher.js +0 -104
- package/dist/query-v2/core/machines/index.d.ts +0 -8
- package/dist/query-v2/core/machines/index.js +0 -8
- package/dist/query-v2/core/resource/ResourceV2.d.ts +0 -120
- package/dist/query-v2/core/resource/ResourceV2.js +0 -464
- package/dist/query-v2/core/resource/ResourceV2Agent.d.ts +0 -26
- package/dist/query-v2/core/resource/ResourceV2Agent.js +0 -132
- package/dist/query-v2/core/resource/index.d.ts +0 -2
- package/dist/query-v2/core/resource/index.js +0 -2
- package/dist/query-v2/index.d.ts +0 -11
- package/dist/query-v2/index.js +0 -17
- package/dist/query-v2/lib/NO_VALUE.d.ts +0 -2
- package/dist/query-v2/lib/NO_VALUE.js +0 -1
- package/dist/query-v2/lib/SKIP_TOKEN.d.ts +0 -2
- package/dist/query-v2/lib/SKIP_TOKEN.js +0 -1
- package/dist/query-v2/lib/index.d.ts +0 -4
- package/dist/query-v2/lib/index.js +0 -3
- package/dist/query-v2/plugins/ReactHooksPlugin.d.ts +0 -25
- package/dist/query-v2/plugins/ReactHooksPlugin.js +0 -19
- package/dist/query-v2/plugins/types.d.ts +0 -1
- package/dist/query-v2/react/__tests__/helpers.d.ts +0 -12
- package/dist/query-v2/react/__tests__/helpers.js +0 -33
- package/dist/query-v2/react/index.d.ts +0 -2
- package/dist/query-v2/react/index.js +0 -2
- package/dist/query-v2/react/useResourceV2Agent.d.ts +0 -12
- package/dist/query-v2/react/useResourceV2Agent.js +0 -36
- package/dist/query-v2/react/useResourceV2Ref.d.ts +0 -12
- package/dist/query-v2/react/useResourceV2Ref.js +0 -57
- package/dist/query-v2/snapshot/Snapshot.d.ts +0 -13
- package/dist/query-v2/snapshot/Snapshot.js +0 -76
- package/dist/query-v2/types/agent.types.d.ts +0 -54
- package/dist/query-v2/types/api.types.d.ts +0 -22
- package/dist/query-v2/types/cache.types.d.ts +0 -37
- package/dist/query-v2/types/index.d.ts +0 -9
- package/dist/query-v2/types/index.js +0 -9
- package/dist/query-v2/types/lifecycle.types.d.ts +0 -25
- package/dist/query-v2/types/machine.types.d.ts +0 -67
- package/dist/query-v2/types/plugin.types.d.ts +0 -38
- package/dist/query-v2/types/resource.types.d.ts +0 -35
- package/dist/query-v2/types/resource.types.js +0 -1
- package/dist/query-v2/types/shared.types.d.ts +0 -20
- package/dist/query-v2/types/shared.types.js +0 -1
- package/dist/query-v2/types/snapshot.types.d.ts +0 -21
- package/dist/query-v2/types/snapshot.types.js +0 -1
- package/docs/contributing/query-v2/README.md +0 -379
- package/docs/migrations/query-v2.md +0 -171
- package/docs/query-v2/README.md +0 -280
- package/docs/query-v2/api-reference.md +0 -235
- package/docs/query-v2/optimistic-updates.md +0 -148
- package/docs/query-v2/ssr.md +0 -130
- /package/dist/{query-v2 → query}/lib/stableStringify.d.ts +0 -0
- /package/dist/{query-v2/plugins/types.js → query/types/api.js} +0 -0
- /package/dist/{query-v2/types/agent.types.js → query/types/cache.js} +0 -0
- /package/dist/{query-v2/types/api.types.js → query/types/command.js} +0 -0
- /package/dist/{query-v2/types/cache.types.js → query/types/common.js} +0 -0
- /package/dist/{query-v2/types/lifecycle.types.js → query/types/plugin-hkt.js} +0 -0
- /package/dist/{query-v2/types/machine.types.js → query/types/resource.js} +0 -0
- /package/dist/{query-v2/types/plugin.types.js → query/types/state.js} +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Оптимистичные обновления (Patching)
|
|
2
|
+
|
|
3
|
+
Патч мгновенно применяет изменения к данным в [кэше][cache], не дожидаясь ответа сервера.
|
|
4
|
+
Пользователь видит результат сразу; при ошибке — данные откатываются.
|
|
5
|
+
Механизм основан на Immer.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Жизненный цикл патча
|
|
9
|
+
|
|
10
|
+
```mermaid
|
|
11
|
+
stateDiagram-v2
|
|
12
|
+
[*] --> pending : queryCacheEntry.createPatch(patchFn)
|
|
13
|
+
pending --> committed : handle.commit()
|
|
14
|
+
pending --> aborted : handle.abort()
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- **pending** — патч создан, изменения отражены в `data`, но сервер ещё не подтвердил операцию.
|
|
18
|
+
- **committed** — сервер подтвердил; патч вливается в базовые данные при следующем ребейсе.
|
|
19
|
+
- **aborted** — операция отменена; `inversePatches` откатывают изменения.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## patchState
|
|
23
|
+
|
|
24
|
+
Когда хотя бы один патч активен, в состоянии [машины][machine] появляется поле `patchState`:
|
|
25
|
+
|
|
26
|
+
| Поле | Описание |
|
|
27
|
+
|------|-----------------------------------------------------------------|
|
|
28
|
+
| `originalData` | Данные до применения первого патча |
|
|
29
|
+
| `patches[]` | Стек патчей |
|
|
30
|
+
| `isConsistencyViolation` | Флаг нарушения консистентности (см. ниже) |
|
|
31
|
+
|
|
32
|
+
Пока `patchState` присутствует: `data` = результат применения всех патчей, `originalData` = нетронутые серверные данные. Когда все патчи завершены — `patchState` сбрасывается в `null`.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Стек патчей
|
|
36
|
+
|
|
37
|
+
Каждый вызов `createPatch` добавляет новый патч в стек. `originalData` фиксируется при создании первого патча, но может изменяться, см "Поведение при аборте".
|
|
38
|
+
Immer-рецепты накладываются поверх текущего `data` (уже содержащего предыдущие патчи), а не поверх `originalData`.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Ребейс при обновлении
|
|
42
|
+
|
|
43
|
+
Когда сервер возвращает свежие данные (переход `refreshing → success`), патчи переигрываются на новой базе.
|
|
44
|
+
Если после ребейса pending-патчей не осталось — `patchState` очищается.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## Поведение при аборте
|
|
48
|
+
|
|
49
|
+
Аборт требует применения обратного immer-патча к `originalData`.
|
|
50
|
+
Но при наличии множественных патчей, происходит переигрывание всех патчей поверх `originalData`.
|
|
51
|
+
|
|
52
|
+
**Для упрощения расчетов (при переигрывании)**:
|
|
53
|
+
- все независимые завершенные патчи (те все `committed` и `aborted`, которые были созданы до первого `pending`) - вливаются в `originalData` и удаляются из стека;
|
|
54
|
+
- все `pending` патчи и зависимые от них `committed` и `aborted` - переигрываются поверх обновленного `originalData`, результат сохраняется в `data`, эти патчи остаются в стеке;
|
|
55
|
+
- если не было `pending` патчей, то `patchState` очищается.
|
|
56
|
+
|
|
57
|
+
В результате переигрывания, может возникнуть нарушение консистентности (см. ниже).
|
|
58
|
+
|
|
59
|
+
## Нарушение консистентности
|
|
60
|
+
|
|
61
|
+
Если при ребейсе или аборте нарушится порядок зависимых патчей (immer не может разрешить изменения) — выставляется `isConsistencyViolation = true`.
|
|
62
|
+
В этом случае стек патчей очищается (`data` остается пропатченой) и запускается автоматическая инвалидация.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Декларативный API
|
|
66
|
+
|
|
67
|
+
С типичным сценарием «мутация + оптимистичное обновление» описано в руководстве по [связям][links].
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## См. также
|
|
71
|
+
|
|
72
|
+
- [Машина состояний][machine] — патчи применяются к состояниям `success`, `refreshing`, `refresh-error`.
|
|
73
|
+
- [Кэш][cache] — запись кэша хранит `patchState` и предоставляет `createPatch`.
|
|
74
|
+
- [Broadcast][broadcast] — кросс-табовая синхронизация, включая передачу патчей.
|
|
75
|
+
- [Использование ресурсов][usage-res] — оптимистичные обновления в контексте ресурса.
|
|
76
|
+
- [Использование команд][usage-cmd] — оптимистичные обновления в контексте команды.
|
|
77
|
+
- [Связи (links)][links] — декларативный `optimisticUpdate` через `link()`.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
[machine]: machine.md
|
|
81
|
+
[cache]: cache.md
|
|
82
|
+
[broadcast]: ../usage/broadcast.md
|
|
83
|
+
[usage-res]: ../usage/resource.md
|
|
84
|
+
[usage-cmd]: ../usage/command.md
|
|
85
|
+
[links]: ../usage/links.md
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Кросс-табовая синхронизация (Broadcast)
|
|
2
|
+
|
|
3
|
+
Кросс-табовая синхронизация позволяет нескольким вкладкам браузера разделять состояние кэша.
|
|
4
|
+
Когда вкладке нужны данные, она перед выполнением `queryFn` отправляет запрос (REQ) через `syncDriver`;
|
|
5
|
+
если другая вкладка уже располагает данными, она отвечает (RES) — и сетевой запрос не выполняется.
|
|
6
|
+
Внутри это реализовано через хук `beforeQuery`, который `createApi` автоматически внедряет в каждый ресурс с включённой синхронизацией:
|
|
7
|
+
непосредственно перед вызовом `queryFn` хук отправляет REQ через `syncDriver` и, получив RES, возвращает данные из другой вкладки, минуя сеть.
|
|
8
|
+
Синхронизация управляется через `syncDriver` — опцию `createApi`,
|
|
9
|
+
принимающую реализацию интерфейса `ISyncDriver`.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Подключение syncDriver
|
|
13
|
+
|
|
14
|
+
Опция `syncDriver` задаётся при вызове `createApi`:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { createApi, broadcastSyncDriver } from '@fozy-labs/rx-toolkit';
|
|
18
|
+
|
|
19
|
+
const api = createApi({
|
|
20
|
+
keyPrefix: 'my-api',
|
|
21
|
+
defaultSync: 'resources',
|
|
22
|
+
syncDriver: broadcastSyncDriver(),
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## broadcastSyncDriver
|
|
28
|
+
|
|
29
|
+
Встроенная реализация `ISyncDriver` на базе [BroadcastChannel API][broadcast-channel].
|
|
30
|
+
|
|
31
|
+
| Параметр | Тип | По умолчанию | Описание |
|
|
32
|
+
|-----------|----------|-----------------------------|--------------------------------------------------------------------|
|
|
33
|
+
| `channel` | `string` | `"rx-toolkit:{keyPrefix}"` | Имя `BroadcastChannel`. Если не указано, генерируется из `keyPrefix` API. |
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// Канал по умолчанию — "rx-toolkit:my-api"
|
|
37
|
+
const api = createApi({
|
|
38
|
+
keyPrefix: 'my-api',
|
|
39
|
+
syncDriver: broadcastSyncDriver(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Явное имя канала
|
|
43
|
+
const api = createApi({
|
|
44
|
+
keyPrefix: 'my-api',
|
|
45
|
+
syncDriver: broadcastSyncDriver({ channel: 'shared-state' }),
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## Управление синхронизацией
|
|
51
|
+
|
|
52
|
+
Опции определяющие, участвует ли ресурс или команда в синхронизации.
|
|
53
|
+
|
|
54
|
+
| Сущность | Опция | Принимаемое значение | По умолчанию |
|
|
55
|
+
|-------------|---------------|--------------------------------|---------------------|
|
|
56
|
+
| **api** | `defaultSync` | `resources` \| `all` \| `none` | `none` |
|
|
57
|
+
| **Ресурс** | `sync` | `boolean` | **api defaultSync** |
|
|
58
|
+
| **Команда** | `sync` | `boolean` | **api defaultSync** |
|
|
59
|
+
|
|
60
|
+
> Если `syncDriver` не задан в `createApi`, то синхронизация работать не будет.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const getCatalog = api.createResource({
|
|
64
|
+
key: 'catalog',
|
|
65
|
+
queryFn: fetchCatalog,
|
|
66
|
+
sync: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Приватные данные пользователя — отключаем sync
|
|
70
|
+
const getProfile = api.createResource({
|
|
71
|
+
key: 'profile',
|
|
72
|
+
queryFn: fetchProfile,
|
|
73
|
+
sync: false,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Мутация, результат которой нужен всем вкладкам
|
|
77
|
+
const markRead = api.createCommand({
|
|
78
|
+
key: 'markRead',
|
|
79
|
+
queryFn: markNotificationRead,
|
|
80
|
+
sync: true,
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
## Что синхронизируется
|
|
86
|
+
|
|
87
|
+
Когда вкладка получает REQ и запись находится в одном из состояний ниже,
|
|
88
|
+
она отвечает RES с соответствующими данными:
|
|
89
|
+
|
|
90
|
+
| Состояние [машины][machine] | Данные в RES |
|
|
91
|
+
|-----------------------------|----------------|
|
|
92
|
+
| `pending` | — |
|
|
93
|
+
| `success` | `data` |
|
|
94
|
+
| `success` (с патчами) | `originalData` |
|
|
95
|
+
| `error` | — |
|
|
96
|
+
| `refreshing` | — |
|
|
97
|
+
| `refresh-error` | — |
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Кастомный syncDriver
|
|
101
|
+
|
|
102
|
+
`ISyncDriver` — транспортно-агностичный контракт.
|
|
103
|
+
Встроенная реализация — `broadcastSyncDriver`, но можно создать свою (WebSocket, SharedWorker и т. д.).
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
### ISyncDriver
|
|
107
|
+
|
|
108
|
+
| Метод | Сигнатура | Описание |
|
|
109
|
+
|--------------|--------------------------------------------------|-------------------------------------------------------------------------|
|
|
110
|
+
| `connect` | `(onMessage: (msg: ISyncMessage) => void) => void` | Подключиться к каналу. `onMessage` вызывается при получении внешних сообщений |
|
|
111
|
+
| `disconnect` | `() => void` | Отключиться от канала и освободить ресурсы |
|
|
112
|
+
| `send` | `(message: ISyncMessage) => void` | Отправить сообщение |
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
### ISyncMessage
|
|
116
|
+
|
|
117
|
+
| Поле | Тип | Описание |
|
|
118
|
+
|-------------|----------------------------------|-------------------------------------------|
|
|
119
|
+
| `type` | `REQ` \| `RES` | Тип сообщения: запрос, ответ или ошибка |
|
|
120
|
+
| `reqId` | `string` | Идентификатор запроса (для связи REQ-RES) |
|
|
121
|
+
| `keys` | `[<prefix>, <key>, <entry_key>]` | Ключи |
|
|
122
|
+
| `data` | `any` | Данные (для RES) |
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## Полный пример
|
|
126
|
+
|
|
127
|
+
Пример демонстрирует `createApi` с `broadcastSyncDriver`,
|
|
128
|
+
ресурс и команду со связями — и поведение на двух вкладках.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { createApi, broadcastSyncDriver, reactHooksPlugin } from '@fozy-labs/rx-toolkit';
|
|
132
|
+
|
|
133
|
+
const api = createApi({
|
|
134
|
+
keyPrefix: 'main-api',
|
|
135
|
+
syncDriver: broadcastSyncDriver(),
|
|
136
|
+
plugins: [reactHooksPlugin()],
|
|
137
|
+
defaultSync: 'resources', // по умолчанию синхронизировать только ресурсы
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Ресурс — sync: true (наследуется от defaultSync: 'resources')
|
|
141
|
+
const todosResource = api.createResource({
|
|
142
|
+
key: 'todos',
|
|
143
|
+
queryFn: async () => {
|
|
144
|
+
const res = await fetch('/api/todos');
|
|
145
|
+
return res.json();
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
function TodoApp() {
|
|
152
|
+
const { data: todos, isLoading } = todosResource.useResource();
|
|
153
|
+
|
|
154
|
+
if (isLoading) return <p>Загрузка...</p>;
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div>
|
|
158
|
+
<ul>
|
|
159
|
+
{todos.map((t: any) => <li key={t.id}>{t.text}</li>)}
|
|
160
|
+
</ul>
|
|
161
|
+
<button disabled={isSaving} onClick={() => trigger({ text: 'Новая задача' })}>
|
|
162
|
+
Добавить
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
## См. также
|
|
171
|
+
|
|
172
|
+
- [API-справочник][api-readme] — полная таблица опций `createApi`
|
|
173
|
+
- [Связи (Links)][links] — декларативное соединение команд и ресурсов
|
|
174
|
+
- [Патчинг][patching] — механизм оптимистичных обновлений и отката
|
|
175
|
+
- [Ресурс (API)][api-resource] — опции ресурса, включая `sync`
|
|
176
|
+
- [Кэш][cache] — управление кэш-записями и их жизненным циклом
|
|
177
|
+
- [Потоки данных][dataflows] — диаграммы кросс-табовой синхронизации
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
[links]: ./links.md
|
|
182
|
+
[api-readme]: ../api/README.md
|
|
183
|
+
[api-resource]: ../api/resource.md
|
|
184
|
+
[cache]: ../concepts/cache.md
|
|
185
|
+
[dataflows]: ../concepts/dataflows.md
|
|
186
|
+
[machine]: ../concepts/machine.md
|
|
187
|
+
[patching]: ../concepts/patching.md
|
|
188
|
+
[broadcast-channel]: https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Команда (Command)
|
|
2
|
+
|
|
3
|
+
Команда — абстракция для **операций записи** (мутаций): создание, обновление, удаление данных. Для чтения данных используйте [ресурс][resource].
|
|
4
|
+
|
|
5
|
+
Аналог: `useMutation` в TanStack Query, `mutation endpoint` в RTK Query.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Создание команды
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
const addTodoCommand = api.createCommand({
|
|
12
|
+
queryFn: async (args: { text: string }) => {
|
|
13
|
+
const res = await fetch('/api/todos', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16
|
+
body: JSON.stringify(args),
|
|
17
|
+
});
|
|
18
|
+
return res.json();
|
|
19
|
+
},
|
|
20
|
+
links: (link) => link({
|
|
21
|
+
resource: todosResource,
|
|
22
|
+
forwardArgs: () => undefined,
|
|
23
|
+
invalidate: true,
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`queryFn` — единственная обязательная опция. Принимает аргументы мутации, возвращает промис с данными.
|
|
29
|
+
`links` — колбэк, описывающий связи с ресурсами, которые нужно обновить после выполнения команды.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Опции
|
|
33
|
+
|
|
34
|
+
Полный список опций — см. [API-справочник команды][api-command].
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## API команды
|
|
38
|
+
|
|
39
|
+
Полный список методов — см. [API-справочник команды][api-command].
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## React: useCommand
|
|
43
|
+
|
|
44
|
+
Для работы в React подключите `reactHooksPlugin()` при создании API:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { createApi, reactHooksPlugin } from '@fozy-labs/rx-toolkit';
|
|
48
|
+
|
|
49
|
+
const api = createApi({
|
|
50
|
+
plugins: [reactHooksPlugin()],
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`useCommand` — метод на экземпляре команды, доступный после подключения плагина:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
function AddTodoForm() {
|
|
58
|
+
const [trigger, { data, error, isLoading }] = addTodoCommand.useCommand();
|
|
59
|
+
const [text, setText] = React.useState('');
|
|
60
|
+
|
|
61
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
if (!text.trim()) return;
|
|
64
|
+
await trigger({ text });
|
|
65
|
+
setText('');
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<form onSubmit={handleSubmit}>
|
|
70
|
+
<input value={text} onChange={e => setText(e.target.value)} disabled={isLoading} />
|
|
71
|
+
<button disabled={isLoading}>Добавить</button>
|
|
72
|
+
{error && <p>Ошибка: {String(error)}</p>}
|
|
73
|
+
</form>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Поведение хука:
|
|
79
|
+
|
|
80
|
+
1. Хук не запускает запрос при монтировании — мутация выполняется только при вызове `trigger`.
|
|
81
|
+
2. `trigger(args)` запускает `queryFn` и возвращает `Promise<TData>`.
|
|
82
|
+
3. Состояние (`isLoading`, `isSuccess`, `isError`) обновляется реактивно.
|
|
83
|
+
|
|
84
|
+
## Состояния команды
|
|
85
|
+
|
|
86
|
+
`useCommand` возвращает `[trigger, state]`, где `state` содержит:
|
|
87
|
+
|
|
88
|
+
| Поле | Тип | Описание |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `status` | `string` | `'idle'` · `'pending'` · `'success'` · `'error'` |
|
|
91
|
+
| `data` | `TData \| null` | Данные последнего успешного ответа. |
|
|
92
|
+
| `error` | `unknown` | Ошибка последнего запроса. |
|
|
93
|
+
| `isLoading` | `boolean` | `true` при выполнении мутации. |
|
|
94
|
+
| `isSuccess` | `boolean` | `true` когда мутация завершилась успешно. |
|
|
95
|
+
| `isError` | `boolean` | `true` при ошибке мутации. |
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## Императивный API
|
|
99
|
+
|
|
100
|
+
### trigger
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Без ключа — создаётся автоматическая кэш-запись
|
|
104
|
+
const data = await addTodoCommand.trigger({ text: 'Новая задача' });
|
|
105
|
+
|
|
106
|
+
// С явным ключом — привязывает результат к кэш-записи 'my-mutation-1'
|
|
107
|
+
const data = await addTodoCommand.trigger({ text: 'Новая задача' }, 'my-mutation-1');
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Запускает `queryFn` и возвращает промис с результатом. Необязательный второй аргумент `key` идентифицирует кэш-запись.
|
|
111
|
+
|
|
112
|
+
### getEntry
|
|
113
|
+
|
|
114
|
+
Синхронно возвращает кэш-запись для указанного ключа, или `null` если записи нет.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const entry = addTodoCommand.getEntry('my-mutation-1');
|
|
118
|
+
if (entry) {
|
|
119
|
+
console.log(entry.machine$().data);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### getEntry$
|
|
124
|
+
|
|
125
|
+
Реактивный аналог `getEntry`. Вызывает сигнал внутри, поэтому должен использоваться в реактивном контексте (`Signal.compute`, `Signal.effect` и т. д.). Возвращает кэш-запись или `null`.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const entry$ = Signal.compute(() => addTodoCommand.getEntry$('my-mutation-1'));
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### createAgent
|
|
132
|
+
|
|
133
|
+
Создаёт агент — реактивный наблюдатель за командой. Принимает опциональный `key` для привязки к конкретной кэш-записи.
|
|
134
|
+
Полная таблица методов и статусов — в [API агента команды][api-cmd-agent].
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const agent = addTodoCommand.createAgent('my-mutation-1');
|
|
138
|
+
|
|
139
|
+
// trigger через агент
|
|
140
|
+
agent.trigger({ text: 'New todo' });
|
|
141
|
+
// agent.state$() → { status: "pending", data: null, isLoading: true, ... }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## Кэш-ключ команды
|
|
146
|
+
|
|
147
|
+
Кэш-ключ команды — это строка.
|
|
148
|
+
По умолчанию ключ генерируется автоматически (таймстамп + индекс при нескольких вызовах в одном таймстампе),
|
|
149
|
+
поэтому каждый вызов создаёт отдельную кэш-запись.
|
|
150
|
+
|
|
151
|
+
Способ указания ключа зависит от API:
|
|
152
|
+
|
|
153
|
+
- **Императивно** — ключ передаётся вторым аргументом в метод `trigger`:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const data = await addTodoCommand.trigger({ text: 'Задача' }, 'my-mutation-1');
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- **React-хук** — ключ задаётся на уровне `useCommand`, а функция `trigger` вызывается только с `args`:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
const [trigger, state] = addTodoCommand.useCommand('my-mutation-1');
|
|
163
|
+
await trigger({ text: 'Задача' });
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- **Агент** — ключ передаётся в `createAgent` и может меняться с помощью методов `trigger` или `setKey`:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const agent = addTodoCommand.createAgent('my-mutation-1');
|
|
170
|
+
agent.trigger({ text: 'Задача' }, 'my-mutation-2');
|
|
171
|
+
agent.setKey('my-mutation-3');
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Разные потребители могут синхронизировать состояние, используя один и тот же ключ.
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
## Связи (Links)
|
|
178
|
+
|
|
179
|
+
Связи позволяют декларативно связать команду с ресурсами — подробнее в [руководстве по связям][links].
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
## Хуки жизненного цикла
|
|
183
|
+
|
|
184
|
+
Хуки позволяют реагировать на события кэша — подробнее в [руководстве по жизненному циклу][lifecycle].
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
## См. также
|
|
188
|
+
|
|
189
|
+
- [Ресурс][resource] — чтение данных с кэшированием и SWR
|
|
190
|
+
- [Машина состояний][machine] — детали переходов между статусами
|
|
191
|
+
- [Система кэширования][cache] — жизненный цикл записей кэша
|
|
192
|
+
- [Агент][agent] — реактивный наблюдатель, транслирующий состояние в UI
|
|
193
|
+
- [Broadcast][broadcast] — синхронизация между вкладками; команды поддерживают опцию `sync: true`
|
|
194
|
+
|
|
195
|
+
[resource]: ./resource.md
|
|
196
|
+
[machine]: ../concepts/machine.md
|
|
197
|
+
[cache]: ../concepts/cache.md
|
|
198
|
+
[agent]: ../concepts/agent.md
|
|
199
|
+
[broadcast]: ./broadcast.md
|
|
200
|
+
[api-command]: ../api/command.md
|
|
201
|
+
[api-cmd-agent]: ../api/command-agent.md
|
|
202
|
+
[lifecycle]: ./lifecycle.md
|
|
203
|
+
[links]: ./links.md
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Хуки жизненного цикла (Lifecycle Hooks)
|
|
2
|
+
|
|
3
|
+
Хуки жизненного цикла позволяют реагировать на события кэш-записей.
|
|
4
|
+
Доступны как для [ресурсов][resource], так и для [команд][command].
|
|
5
|
+
Полный список параметров — в API-справочнике ([ресурс][api-resource],
|
|
6
|
+
[команда][api-command]).
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## onCacheEntryAdded
|
|
10
|
+
|
|
11
|
+
Вызывается **один раз** при создании кэш-записи для конкретных аргументов.
|
|
12
|
+
Колбэк получает `args` и объект контекста `ctx`:
|
|
13
|
+
|
|
14
|
+
| Свойство ctx | Тип | Описание |
|
|
15
|
+
|---|-------------------------------------|---------------------------------------------------------------------------------------------------------|
|
|
16
|
+
| `entry` | `CacheEntry` (ресурса или команды) | Текущая кэш-запись. |
|
|
17
|
+
| `$cacheDataLoaded` | `Promise<TData>` | Разрешается при первом успешном ответе. Отклоняется, если запись удалена до успешного получения данных. |
|
|
18
|
+
| `$cacheEntryRemoved` | `Promise<void>` | Разрешается при удалении записи из кэша. |
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const messagesResource = api.createResource({
|
|
23
|
+
queryFn: (chatId: string) => fetch(`/api/chats/${chatId}/messages`).then(r => r.json()),
|
|
24
|
+
onCacheEntryAdded: async (chatId, { $cacheDataLoaded, $cacheEntryRemoved }) => {
|
|
25
|
+
console.log('Cache entry added for chatId:', chatId);
|
|
26
|
+
|
|
27
|
+
await $cacheDataLoaded;
|
|
28
|
+
|
|
29
|
+
console.log('Initial data loaded for chatId:', chatId);
|
|
30
|
+
|
|
31
|
+
await $cacheEntryRemoved;
|
|
32
|
+
|
|
33
|
+
console.log('Cache entry removed for chatId:', chatId);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Хук работает аналогично и для [команд][command].
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## onQueryStarted
|
|
42
|
+
|
|
43
|
+
Функция, которая вызывается при запуске каждого отдельного запроса.
|
|
44
|
+
Позволяет выполнять код на протяжении всего жизненного цикла отдельного запроса.
|
|
45
|
+
|
|
46
|
+
Колбэк получает `args` и объект контекста `ctx`:
|
|
47
|
+
|
|
48
|
+
| Свойство ctx | Тип | Описание |
|
|
49
|
+
|---|-------------------------------------|----------------------------------------------------------------------|
|
|
50
|
+
| `entry` | `CacheEntry` (ресурса или команды) | Текущая кэш-запись. |
|
|
51
|
+
| `$queryFulfilled` | `Promise<{ data: TData }>` | Разрешается с данными при успехе. Отклоняется при ошибке или аборте. |
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const userResource = api.createResource({
|
|
55
|
+
queryFn: (id: number) => fetch(`/api/users/${id}`).then(r => r.json()),
|
|
56
|
+
onQueryStarted: async (args, { $queryFulfilled }) => {
|
|
57
|
+
console.log('Query started with args:', args);
|
|
58
|
+
|
|
59
|
+
const { data } = await $queryFulfilled;
|
|
60
|
+
|
|
61
|
+
console.log('Query succeeded with data:', data);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Хук работает аналогично и для [команд][command].
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## Обработка ошибок
|
|
70
|
+
|
|
71
|
+
Ошибки внутри колбэков **подавляются автоматически** — не выбрасываются наружу и не влияют на основной поток запроса.
|
|
72
|
+
При необходимости (например, для предотвращения утечек памяти) оборачивайте `$queryFulfilled` и `$cacheDataLoaded` в `try/catch`:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
onCacheEntryAdded: async (id, { $cacheDataLoaded, entry }) => {
|
|
76
|
+
try {
|
|
77
|
+
const connection = createUserConnection(id);
|
|
78
|
+
const { data } = await $cacheDataLoaded;
|
|
79
|
+
} catch {
|
|
80
|
+
connection.close('unused');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
connection.onUserUpdated((partialUser) => {
|
|
85
|
+
const patch = entry.patch((draft) => {
|
|
86
|
+
Object.assign(draft, partialUser);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
path.commit();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await $cacheEntryRemoved;
|
|
93
|
+
|
|
94
|
+
connection.close('disposed');
|
|
95
|
+
},
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
## См. также
|
|
100
|
+
|
|
101
|
+
- [Кэш][cache] — управление кэш-записями и их жизненным циклом
|
|
102
|
+
- [Ресурс][resource] — чтение данных и кэширование
|
|
103
|
+
- [Команда][command] — мутации и побочные эффекты
|
|
104
|
+
- [Потоки данных][dataflows] — диаграммы жизненного цикла запросов
|
|
105
|
+
- [API: ресурс][api-resource] — полная таблица параметров ресурса
|
|
106
|
+
- [API: команда][api-command] — полная таблица параметров команды
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
[cache]: ../concepts/cache.md
|
|
110
|
+
[resource]: ./resource.md
|
|
111
|
+
[command]: ./command.md
|
|
112
|
+
[dataflows]: ../concepts/dataflows.md
|
|
113
|
+
[api-resource]: ../api/resource.md
|
|
114
|
+
[api-command]: ../api/command.md
|