@fozy-labs/rx-toolkit 0.5.4 → 0.6.2
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 +3 -0
- package/dist/query/core/api/index.js +3 -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 +54 -2
- package/docs/contributing/release/README.md +2 -8
- 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 +5 -7
- 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,125 @@
|
|
|
1
|
+
# Связи (Links)
|
|
2
|
+
|
|
3
|
+
Связь (link) — декларативное соединение между [командой][command] и [ресурсом][resource]. После мутации связь автоматически синхронизирует кэш затронутых ресурсов: инвалидирует, обновляет оптимистично или подставляет данные из ответа сервера.
|
|
4
|
+
|
|
5
|
+
Связь определяется в колбэке `links` при создании **команды**. Функция `link` принимает конфигурацию с целевым ресурсом и стратегией обновления.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Параметры конфигурации
|
|
9
|
+
|
|
10
|
+
| Параметр | Тип | Обязательный | Описание |
|
|
11
|
+
|----------|-----|-------------|----------|
|
|
12
|
+
| `resource` | `IResource<TResArgs, TResData>` | да | Целевой [ресурс][resource] |
|
|
13
|
+
| `forwardArgs` | `(commandArgs: TArgs) => TResArgs \| undefined` | да | Маппинг аргументов команды в ключ кэша ресурса. `undefined` — все записи |
|
|
14
|
+
| `invalidate` | `boolean` | нет | Инвалидировать запись после успеха команды |
|
|
15
|
+
| `optimisticUpdate` | `(draft: TResData, commandArgs: TArgs) => void` | нет | Immer-рецепт, применяется немедленно |
|
|
16
|
+
| `update` | `(draft: TResData, commandArgs: TArgs, result: TData) => void` | нет | Immer-рецепт, применяется после успеха |
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## forwardArgs — адресация записей кэша
|
|
20
|
+
|
|
21
|
+
`forwardArgs` — единственное обязательное поле.
|
|
22
|
+
Оно преобразует аргументы команды в ключ кэш-записи ресурса, определяя, **какие именно записи** затронет связь.
|
|
23
|
+
|
|
24
|
+
Адресация конкретной записи:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
link({
|
|
28
|
+
resource: userResource,
|
|
29
|
+
forwardArgs: (args) => args.userId,
|
|
30
|
+
invalidate: true,
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> Если `forwardArgs` возвращает аргументы, для которых записи в кэше ещё нет — связь не сработает (нечего обновлять/инвалидировать).
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Тайминг выполнения
|
|
38
|
+
|
|
39
|
+
Стратегии выполняются в разные моменты жизненного цикла команды:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
trigger(args)
|
|
43
|
+
│
|
|
44
|
+
├── optimisticUpdate ← немедленно, до ответа сервера
|
|
45
|
+
│
|
|
46
|
+
├── queryFn(args) ← сетевой запрос
|
|
47
|
+
│
|
|
48
|
+
├─ OK ─┬── update ← после успешного ответа (получает result)
|
|
49
|
+
│ └── invalidate ← после успешного ответа (помечает запись устаревшей)
|
|
50
|
+
│
|
|
51
|
+
└─ ERROR ── rollback ← автоматический откат optimisticUpdate
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- **optimisticUpdate** — применяется мгновенно, UI обновляется без ожидания. Использует Immer-патч; при ошибке команды откат происходит автоматически через систему [патчинга][patching].
|
|
55
|
+
- **update** — применяется после успеха, получает `result` из ответа сервера.
|
|
56
|
+
- **invalidate** — помечает запись устаревшей после успеха; ресурс будет перезапрошен при следующем обращении.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Комбинирование стратегий
|
|
60
|
+
|
|
61
|
+
### В одной связи
|
|
62
|
+
|
|
63
|
+
`optimisticUpdate` и `invalidate` можно объединить: UI обновляется мгновенно, а после успеха кэш инвалидируется и перезапрашивается с сервера. Это даёт и мгновенный отклик, и гарантию консистентности:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const updateTodoCommand = api.createCommand({
|
|
67
|
+
queryFn: fetchTodoUpdate,
|
|
68
|
+
links: (link) => {
|
|
69
|
+
link({
|
|
70
|
+
resource: todosResource,
|
|
71
|
+
forwardArgs: () => undefined,
|
|
72
|
+
optimisticUpdate: (draft, args) => {
|
|
73
|
+
const todo = draft.find((t: any) => t.id === args.id);
|
|
74
|
+
if (todo) todo.done = args.done;
|
|
75
|
+
},
|
|
76
|
+
invalidate: true,
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### Несколько связей на одну команду
|
|
84
|
+
|
|
85
|
+
Команда может затрагивать несколько ресурсов. Каждый ресурс получает собственную связь:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const deleteProjectCommand = api.createCommand({
|
|
89
|
+
queryFn: fetchProjectDelete,
|
|
90
|
+
links: (link) => {
|
|
91
|
+
link({
|
|
92
|
+
resource: projectResource,
|
|
93
|
+
forwardArgs: (args) => args.projectId,
|
|
94
|
+
invalidate: true,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
link({
|
|
98
|
+
resource: projectListResource,
|
|
99
|
+
forwardArgs: () => undefined,
|
|
100
|
+
optimisticUpdate: (draft, args) => {
|
|
101
|
+
const idx = draft.findIndex((p: any) => p.id === args.projectId);
|
|
102
|
+
if (idx !== -1) draft.splice(idx, 1);
|
|
103
|
+
},
|
|
104
|
+
invalidate: true,
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## См. также
|
|
112
|
+
|
|
113
|
+
- [Команда][command] — создание команд, опции и React-хук `useCommand`
|
|
114
|
+
- [Ресурс][resource] — чтение данных и кэширование
|
|
115
|
+
- [Патчинг][patching] — механизм оптимистичных обновлений и отката
|
|
116
|
+
- [Broadcast][broadcast] — кросс-табовая синхронизация состояния
|
|
117
|
+
- [Потоки данных][dataflows] — общая схема движения данных в Query
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
[command]: ./command.md
|
|
122
|
+
[resource]: ./resource.md
|
|
123
|
+
[patching]: ../concepts/patching.md
|
|
124
|
+
[broadcast]: ./broadcast.md
|
|
125
|
+
[dataflows]: ../concepts/dataflows.md
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Плагины
|
|
2
|
+
|
|
3
|
+
Плагины расширяют возможности API, добавляя методы к [ресурсам][resource] и [командам][command]. Например, встроенный плагин `reactHooksPlugin()` добавляет React-хуки прямо на экземпляры ресурсов.
|
|
4
|
+
|
|
5
|
+
Плагины передаются при создании API через опцию `plugins`:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { createApi, reactHooksPlugin } from '@fozy-labs/rx-toolkit';
|
|
9
|
+
|
|
10
|
+
const api = createApi({
|
|
11
|
+
plugins: [reactHooksPlugin()],
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## reactHooksPlugin()
|
|
17
|
+
|
|
18
|
+
Встроенный плагин для интеграции с React. Добавляет хук `useResource` на каждый ресурс, созданный через API:
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
const usersResource = api.createResource({
|
|
22
|
+
queryFn: (args: { page: number }, signal) =>
|
|
23
|
+
fetch(`/api/users?page=${args.page}`, { signal }).then(r => r.json()),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Хук доступен благодаря плагину:
|
|
27
|
+
const { data, isLoading } = usersResource.useResource({ page: 1 });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Подробнее о поведении хука — см. раздел «React: useResource» в документации [ресурса][resource].
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Написание собственного плагина
|
|
34
|
+
|
|
35
|
+
Плагин реализует интерфейс `IPlugin`:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
interface IPlugin {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
install(context: IPluginContext): void;
|
|
41
|
+
augmentResource?<TArgs, TData>(
|
|
42
|
+
resource: IResource<TArgs, TData>,
|
|
43
|
+
options: TResourceOptions<TArgs, TData>,
|
|
44
|
+
): Record<string, unknown>;
|
|
45
|
+
augmentCommand?<TArgs, TData>(
|
|
46
|
+
command: ICommand<TArgs, TData>,
|
|
47
|
+
options: TCommandOptions<TArgs, TData>,
|
|
48
|
+
): Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- `name` — уникальное строковое имя плагина.
|
|
53
|
+
- `install(context)` — вызывается один раз при `createApi()`. Получает `IPluginContext` с метаинформацией об API.
|
|
54
|
+
- `augmentResource(resource, options)` — вызывается при каждом `createResource()`. Возвращает объект с методами, которые будут добавлены к ресурсу.
|
|
55
|
+
- `augmentCommand(command, options)` — аналогично, вызывается при каждом `createCommand()`. Возвращает объект с методами для команды.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const loggingPlugin: IPlugin = {
|
|
59
|
+
name: 'LoggingPlugin',
|
|
60
|
+
install() {},
|
|
61
|
+
augmentResource(resource) {
|
|
62
|
+
return {
|
|
63
|
+
logState(args: unknown) {
|
|
64
|
+
// Упрощённый пример — getEntry$ принимает аргументы для идентификации кэш-записи
|
|
65
|
+
console.log(resource.getEntry(args));
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## Типизация вкладов плагина
|
|
74
|
+
|
|
75
|
+
Чтобы TypeScript знал о методах, добавленных плагином, используется паттерн условных типов через `PluginResourceContributions`:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
type PluginResourceContributions<TPlugin, TArgs, TData> =
|
|
79
|
+
TPlugin extends { name: 'ReactHooksPlugin' }
|
|
80
|
+
? IReactHooksPluginContributions<TArgs, TData>
|
|
81
|
+
: Record<string, never>;
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Тип `PluginAugmentations` объединяет вклады всех плагинов в массиве и добавляет их к возвращаемому типу `createResource()`. Благодаря этому `usersResource.useResource(...)` корректно типизирован, когда в `plugins` передан `reactHooksPlugin()`.
|
|
85
|
+
|
|
86
|
+
Для собственного плагина добавьте новую ветку в `PluginResourceContributions`, аналогичную ветке `ReactHooksPlugin`.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## См. также
|
|
90
|
+
|
|
91
|
+
- [Ресурс][resource] — основной примитив запросов, который расширяется плагинами.
|
|
92
|
+
- [Команда][command] — примитив мутаций, также расширяемый через плагины.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
[resource]: ./resource.md
|
|
96
|
+
[command]: ./command.md
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Ресурс (Resource)
|
|
2
|
+
|
|
3
|
+
Ресурс — абстракция для **чтения данных** с автоматическим кэшированием и stale-while-revalidate (SWR). Для операций записи используйте [команду][command].
|
|
4
|
+
|
|
5
|
+
Аналог: `useQuery` в TanStack Query, `query endpoint` в RTK Query.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Создание ресурса
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
const usersResource = api.createResource({
|
|
12
|
+
queryFn: async (args: { page: number }, abortSignal) => {
|
|
13
|
+
const res = await fetch(`/api/users?page=${args.page}`, { signal: abortSignal });
|
|
14
|
+
return res.json();
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`queryFn` — единственная обязательная опция. Принимает аргументы запроса и `AbortSignal`, возвращает промис с данными. При отмене запроса (смена аргументов, размонтирование) сигнал срабатывает автоматически.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Опции
|
|
23
|
+
|
|
24
|
+
Полный список опций — см. [API-справочник ресурса][api-resource].
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## API ресурса
|
|
28
|
+
|
|
29
|
+
Полный список методов — см. [API-справочник ресурса][api-resource].
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## React: useResource
|
|
33
|
+
|
|
34
|
+
Для работы в React подключите `reactHooksPlugin()` при создании API:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { createApi, reactHooksPlugin } from '@fozy-labs/rx-toolkit';
|
|
38
|
+
|
|
39
|
+
const api = createApi({
|
|
40
|
+
plugins: [reactHooksPlugin()],
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`useResource` — метод на экземпляре ресурса, доступный после подключения плагина:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
function UsersList({ page }: { page: number }) {
|
|
48
|
+
const { data, error, isLoading } = usersResource.useResource({ page });
|
|
49
|
+
|
|
50
|
+
if (isLoading) return <Spinner />;
|
|
51
|
+
if (error) return <ErrorMessage error={error} />;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<ul>
|
|
55
|
+
{data.map(user => <li key={user.id}>{user.name}</li>)}
|
|
56
|
+
</ul>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Поведение хука:
|
|
62
|
+
|
|
63
|
+
1. При монтировании — запускает запрос с переданными аргументами.
|
|
64
|
+
2. При изменении аргументов — автоматически перезапрашивает данные.
|
|
65
|
+
3. При размонтировании — отписывается. Кэш-запись сохраняется в течение `retentionTime`.
|
|
66
|
+
4. При повторном монтировании с теми же аргументами — данные берутся из кэша мгновенно.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## Условные запросы
|
|
70
|
+
|
|
71
|
+
Передайте `SKIP` вместо аргументов, чтобы отложить запрос:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { SKIP } from '@fozy-labs/rx-toolkit';
|
|
75
|
+
|
|
76
|
+
function UserProfile({ userId }: { userId: string | null }) {
|
|
77
|
+
const { data, isLoading } = userResource.useResource(
|
|
78
|
+
userId ? { id: userId } : SKIP,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!userId) return <p>Выберите пользователя</p>;
|
|
82
|
+
if (isLoading) return <Spinner />;
|
|
83
|
+
return <h1>{data.name}</h1>;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`SKIP` полностью останавливает наблюдение — запрос не выполняется, состояние сбрасывается в `idle`.
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
## Состояния ресурса
|
|
91
|
+
|
|
92
|
+
`useResource` возвращает объект с полями `status`, `data`, `error` и булевыми флагами:
|
|
93
|
+
|
|
94
|
+
| Поле | Тип | Описание |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `status` | `string` | `'idle'` · `'pending'` · `'success'` · `'error'` · `'refreshing'` · `'refresh-error'` |
|
|
97
|
+
| `data` | `TData \| null` | Данные последнего успешного ответа. Сохраняются при `refreshing`. |
|
|
98
|
+
| `error` | `unknown` | Ошибка последнего запроса. |
|
|
99
|
+
| `isLoading` | `boolean` | `true` при любом незавершённом запросе. |
|
|
100
|
+
| `isInitialLoading` | `boolean` | `true` только при первой загрузке (данных ещё нет). |
|
|
101
|
+
| `isSuccess` | `boolean` | `true` когда данные получены. |
|
|
102
|
+
| `isError` | `boolean` | `true` при ошибке. |
|
|
103
|
+
| `isRefreshing` | `boolean` | `true` при фоновом обновлении (SWR). |
|
|
104
|
+
| `isRefreshError` | `boolean` | `true` при ошибке фонового обновления. |
|
|
105
|
+
|
|
106
|
+
### Фоновое обновление (refresh)
|
|
107
|
+
|
|
108
|
+
Вызов `refresh(args)` или `trigger(args, true)` обновляет данные **без потери текущего отображения**. Пользователь продолжает видеть прежние данные, пока в фоне выполняется новый запрос. Когда ответ приходит — данные обновляются на месте; если запрос падает с ошибкой, прежние данные сохраняются, а статус переходит в `refresh-error`.
|
|
109
|
+
|
|
110
|
+
### Плавная смена аргументов (SWR)
|
|
111
|
+
|
|
112
|
+
Когда аргументы `useResource` меняются (например, пользователь переключает страницу), компонент **не сбрасывается в пустое состояние**. Вместо этого на экране остаются данные предыдущего запроса, пока загружаются новые. Как только новые данные готовы, они автоматически заменяют старые.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## Императивный API
|
|
116
|
+
|
|
117
|
+
### trigger
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const data = await usersResource.trigger({ page: 1 });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Запускает `queryFn` и возвращает промис с результатом. Параллельные вызовы с одинаковыми аргументами дедуплицируются — возвращается один и тот же промис.
|
|
124
|
+
|
|
125
|
+
### refresh
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
usersResource.refresh({ page: 1 });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Запускает повторный запрос для кэш-записи. Если у записи есть активные подписчики — запрос выполняется немедленно. Без подписчиков — данные будут перезапрошены при следующем обращении.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
### getEntry
|
|
135
|
+
|
|
136
|
+
Синхронно возвращает кэш-запись для указанных аргументов, или `null` если данные ещё не запрашивались. С флагом `doInitiate = true` — создаёт запись и запускает загрузку, если её ещё нет.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
// Проверить, есть ли данные в кэше
|
|
140
|
+
const entry = usersResource.getEntry({ page: 1 });
|
|
141
|
+
if (entry) {
|
|
142
|
+
console.log(entry.machine$().data);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
### getEntry$
|
|
148
|
+
|
|
149
|
+
Реактивный аналог `getEntry`. Вызывает сигнал внутри, поэтому должен использоваться в реактивном контексте (`Signal.compute`, `Signal.effect` и т. д.). Возвращает кэш-запись или `null`.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const entry$ = Signal.compute(() => usersResource.getEntry$({ page: page$() }));
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### createAgent
|
|
156
|
+
|
|
157
|
+
Агент — реактивный наблюдатель ресурса.
|
|
158
|
+
Он отслеживает текущую и при необходимости предыдущую запись кэша,
|
|
159
|
+
объединяя их в плоский вычисляемый сигнал.
|
|
160
|
+
Агент является строительным блоком для React-хука `useResource` и не требует явного уничтожения — внутренние сигналы деактивируются при потере подписчиков.
|
|
161
|
+
Полная таблица методов и статусов — в [API агента ресурса][api-res-agent].
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const agent = usersResource.createAgent();
|
|
165
|
+
agent.start({ page: 1 });
|
|
166
|
+
// agent.state$() → { status: "pending", data: null, isInitialLoading: true, ... }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
При смене аргументов через `start(newArgs)` агент реализует SWR-поведение:
|
|
170
|
+
если предыдущий запрос содержит данные (статус `success` или `refreshing`),
|
|
171
|
+
они сохраняются в `data`, а `status` переключается на `"refreshing"` до получения нового ответа.
|
|
172
|
+
Это позволяет показывать устаревшие данные вместо пустого состояния.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
agent.start({ page: 2 }); // SWR: data от page:1, status: "refreshing"
|
|
176
|
+
agent.start(SKIP); // idle: data: null, status: "idle"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## Связи (Links)
|
|
181
|
+
|
|
182
|
+
Связи позволяют декларативно связать команду с ресурсами — подробнее в [руководстве по связям][links].
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
## Хуки жизненного цикла
|
|
186
|
+
|
|
187
|
+
Хуки позволяют реагировать на события кэша — подробнее в [руководстве по жизненному циклу][lifecycle].
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
## См. также
|
|
191
|
+
|
|
192
|
+
- [Команда][command] — мутации (создание, обновление, удаление)
|
|
193
|
+
- [Машина состояний запроса][machine] — детали переходов между статусами
|
|
194
|
+
- [Кэш][cache] — система кэширования записей
|
|
195
|
+
- [Агент][agent] — SWR-наблюдатель, связывающий UI с записью кэша
|
|
196
|
+
- [Кросс-табовая синхронизация][broadcast] — синхронизация кэша между вкладками
|
|
197
|
+
|
|
198
|
+
[command]: ./command.md
|
|
199
|
+
[machine]: ../concepts/machine.md
|
|
200
|
+
[api-resource]: ../api/resource.md
|
|
201
|
+
[lifecycle]: ./lifecycle.md
|
|
202
|
+
[links]: ./links.md
|
|
203
|
+
[cache]: ../concepts/cache.md
|
|
204
|
+
[agent]: ../concepts/agent.md
|
|
205
|
+
[api-res-agent]: ../api/resource-agent.md
|
|
206
|
+
[broadcast]: ./broadcast.md
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# SSR и гидрация (Snapshot)
|
|
2
|
+
|
|
3
|
+
Снимок (snapshot) позволяет сериализовать состояние кэша на сервере и передать его клиенту, чтобы избежать повторных запросов при гидрации. Механизм работает как для [ресурсов][resource], так и для [команд][command].
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## getSnapshot()
|
|
7
|
+
|
|
8
|
+
Метод `api.getSnapshot()` собирает **только успешные** записи всех зарегистрированных ресурсов и возвращает объект `TApiSnapshot`:
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
const snapshot = api.getSnapshot();
|
|
12
|
+
// → { version, keyPrefix, timestamp, resources: { ... } }
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Каждая запись содержит `args`, `data` и `updatedAt`. Записи в состояниях pending, error и refreshing — пропускаются.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## initialSnapshot
|
|
19
|
+
|
|
20
|
+
На клиенте снимок передаётся в `createApi`:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
const api = createApi({
|
|
24
|
+
initialSnapshot: snapshot,
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Гидрация происходит **лениво**: при вызове `createResource()` библиотека ищет соответствующий slice в снимке и восстанавливает кэш-записи. После гидрации slice удаляется из внутреннего хранилища (consume-паттерн). Вызов `resetAll()` обнуляет сохранённый снимок целиком.
|
|
29
|
+
|
|
30
|
+
При загрузке проверяется `version` и `keyPrefix` — при несовпадении выбрасывается ошибка.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## snapshotValidTime
|
|
34
|
+
|
|
35
|
+
Опция `snapshotValidTime` определяет, сколько миллисекунд данные из снимка считаются актуальными. Доступна на уровне [API][api-readme] и на уровне отдельного ресурса (ресурс-уровень приоритетнее).
|
|
36
|
+
|
|
37
|
+
| Значение | Поведение |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `false` (по умолчанию) | Данные из снимка считаются всегда валидными. |
|
|
40
|
+
| `number` (мс) | Если `Date.now() - updatedAt > snapshotValidTime`, запись автоматически инвалидируется после гидрации. |
|
|
41
|
+
|
|
42
|
+
Инвалидация запускает перезапрос — компонент получит свежие данные без дополнительного кода.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Жизненный цикл
|
|
46
|
+
|
|
47
|
+
1. **Сервер** — `api.getSnapshot()` сериализует успешные записи кэша в `TApiSnapshot`.
|
|
48
|
+
2. **Передача** — снимок передаётся клиенту как JSON (через `<script>`, props, cookie и т.д.).
|
|
49
|
+
3. **Создание API** — `createApi({ initialSnapshot })` валидирует версию и keyPrefix, сохраняет deep-клон.
|
|
50
|
+
4. **Гидрация ресурсов** — каждый `createResource()` достаёт свой slice и восстанавливает записи в [кэш][cache].
|
|
51
|
+
5. **Авто-инвалидация** — если задан `snapshotValidTime` и запись устарела, она инвалидируется с перезапросом.
|
|
52
|
+
6. **Consume** — использованный slice удаляется; `resetAll()` обнуляет снимок целиком.
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Что сериализуется
|
|
56
|
+
|
|
57
|
+
Каждая [успешная запись][cache] содержит три поля:
|
|
58
|
+
|
|
59
|
+
| Поле | Описание |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `args` | Аргументы запроса |
|
|
62
|
+
| `data` | Данные ответа |
|
|
63
|
+
| `updatedAt` | Время последнего успешного ответа |
|
|
64
|
+
|
|
65
|
+
Версия снимка (`version`) проверяется при загрузке — при несовпадении гидрация невозможна.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## См. также
|
|
69
|
+
|
|
70
|
+
- [Кэш][cache] — управление кэш-записями и жизненный цикл GC
|
|
71
|
+
- [Машина состояний][machine] — восстановление из снимка через `Machine.fromSnapshot()`
|
|
72
|
+
- [API-справочник][api-readme] — таблица опций `initialSnapshot`, `snapshotValidTime`, `getSnapshot()`
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
[resource]: ./resource.md
|
|
77
|
+
[command]: ./command.md
|
|
78
|
+
[cache]: ../concepts/cache.md
|
|
79
|
+
[machine]: ../concepts/machine.md
|
|
80
|
+
[api-readme]: ../api/README.md
|