@fozy-labs/rx-toolkit 0.5.4 → 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 +48 -2
- 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,136 @@
|
|
|
1
|
+
import { Machine } from "../machine/Machine";
|
|
2
|
+
import { CacheEntry } from "./CacheEntry";
|
|
3
|
+
// ==================== QueryCacheEntry ====================
|
|
4
|
+
export class QueryCacheEntry extends CacheEntry {
|
|
5
|
+
keyedArgs;
|
|
6
|
+
machine$;
|
|
7
|
+
_queryFn;
|
|
8
|
+
_abortController = null;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
const machine = options.initialMachine ?? Machine.pending(options.keyedArgs.value);
|
|
11
|
+
const devtoolsKey = options.resourceKey
|
|
12
|
+
? `${options.resourceKey}:${options.keyedArgs.key}`
|
|
13
|
+
: options.keyedArgs.key;
|
|
14
|
+
super(machine, {
|
|
15
|
+
retentionTime: options.retentionTime,
|
|
16
|
+
devtoolsKey,
|
|
17
|
+
beforeDevtoolsPush: options.beforeDevtoolsPush,
|
|
18
|
+
});
|
|
19
|
+
this.keyedArgs = options.keyedArgs;
|
|
20
|
+
this._queryFn = options.queryFn;
|
|
21
|
+
this.machine$ = this.state$;
|
|
22
|
+
// Auto-execute queryFn when no initial state is provided
|
|
23
|
+
if (!options.initialMachine) {
|
|
24
|
+
this._execute();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Transition to refreshing and re-fetch data. Valid from success or refresh-error. */
|
|
28
|
+
refresh() {
|
|
29
|
+
const machine = this.machine$.peek();
|
|
30
|
+
if (machine.status !== "success" && machine.status !== "refresh-error") {
|
|
31
|
+
console.warn(`[QueryCacheEntry] refresh() called in invalid state: ${machine.status}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.set(machine.refresh());
|
|
35
|
+
this._execute();
|
|
36
|
+
}
|
|
37
|
+
/** Re-execute query after error. Valid from error state only. */
|
|
38
|
+
retry() {
|
|
39
|
+
const machine = this.machine$.peek();
|
|
40
|
+
if (machine.status !== "error") {
|
|
41
|
+
console.warn(`[QueryCacheEntry] retry() called in invalid state: ${machine.status}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.set(machine.retry());
|
|
45
|
+
this._execute();
|
|
46
|
+
}
|
|
47
|
+
/** Create an optimistic patch. Returns null if state has no data. */
|
|
48
|
+
createPatch(patchFn) {
|
|
49
|
+
const machine = this.machine$.peek();
|
|
50
|
+
if (machine.status !== "success" && machine.status !== "refreshing" && machine.status !== "refresh-error") {
|
|
51
|
+
console.warn(`[QueryCacheEntry] createPatch() called in invalid state: ${machine.status}`);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const onSettle = () => {
|
|
55
|
+
const current = this.machine$.peek();
|
|
56
|
+
if ((current.status === "success" ||
|
|
57
|
+
current.status === "refreshing" ||
|
|
58
|
+
current.status === "refresh-error") &&
|
|
59
|
+
current.patchState) {
|
|
60
|
+
const finished = current.finishPatch();
|
|
61
|
+
this.set(finished);
|
|
62
|
+
if (finished.patchState?.isConsistencyViolation) {
|
|
63
|
+
this.refresh();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const { machine: newMachine, handle } = machine.createPatch(patchFn, onSettle);
|
|
68
|
+
this.set(newMachine);
|
|
69
|
+
return handle;
|
|
70
|
+
}
|
|
71
|
+
/** Abort any in-flight request before completing the entry. */
|
|
72
|
+
complete() {
|
|
73
|
+
this._abortController?.abort();
|
|
74
|
+
super.complete();
|
|
75
|
+
}
|
|
76
|
+
// ==================== Private ====================
|
|
77
|
+
/** @internal Called by Resource when beforeQuery intercept needs to trigger the query. */
|
|
78
|
+
_execute() {
|
|
79
|
+
// Abort any in-flight request
|
|
80
|
+
this._abortController?.abort();
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
this._abortController = controller;
|
|
83
|
+
const machine = this.machine$.peek();
|
|
84
|
+
switch (machine.status) {
|
|
85
|
+
case "success":
|
|
86
|
+
this.set(machine.refresh());
|
|
87
|
+
break;
|
|
88
|
+
case "refresh-error":
|
|
89
|
+
this.set(machine.refresh());
|
|
90
|
+
break;
|
|
91
|
+
case "pending":
|
|
92
|
+
case "refreshing":
|
|
93
|
+
break;
|
|
94
|
+
case "error":
|
|
95
|
+
return;
|
|
96
|
+
default:
|
|
97
|
+
console.warn(`[QueryCacheEntry] executed in unexpected state: ${machine.status}`);
|
|
98
|
+
}
|
|
99
|
+
this._queryFn(this.keyedArgs, controller.signal)
|
|
100
|
+
.then((data) => {
|
|
101
|
+
if (controller.signal.aborted)
|
|
102
|
+
return;
|
|
103
|
+
const machine = this.machine$.peek();
|
|
104
|
+
switch (machine.status) {
|
|
105
|
+
case "pending":
|
|
106
|
+
this.set(machine.success(data));
|
|
107
|
+
break;
|
|
108
|
+
case "refreshing": {
|
|
109
|
+
const rebased = machine.rebase(data);
|
|
110
|
+
this.set(rebased);
|
|
111
|
+
if (rebased.patchState?.isConsistencyViolation) {
|
|
112
|
+
this.refresh();
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
default:
|
|
117
|
+
console.warn(`[QueryCacheEntry] received data in unexpected state: ${machine.status}`);
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
.catch((error) => {
|
|
121
|
+
if (controller.signal.aborted)
|
|
122
|
+
return;
|
|
123
|
+
const machine = this.machine$.peek();
|
|
124
|
+
switch (machine.status) {
|
|
125
|
+
case "pending":
|
|
126
|
+
this.set(machine.fail(error));
|
|
127
|
+
break;
|
|
128
|
+
case "refreshing":
|
|
129
|
+
this.set(machine.fail(error));
|
|
130
|
+
break;
|
|
131
|
+
default:
|
|
132
|
+
console.warn(`[QueryCacheEntry] received error in unexpected state: ${machine.status}`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Args, ICommand, ICommandAgent, ICommandConfig } from "../../../query/types";
|
|
2
|
+
import { QueryCacheEntry } from "../cache/QueryCacheEntry";
|
|
3
|
+
/**
|
|
4
|
+
* Abstraction for write operations (mutations).
|
|
5
|
+
*
|
|
6
|
+
* Manages cache entries, link-based optimistic/update patches on related resources,
|
|
7
|
+
* and lifecycle hooks (`onCacheEntryAdded`, `onQueryStarted`).
|
|
8
|
+
*
|
|
9
|
+
* @template TArgs - The argument type accepted by the mutation.
|
|
10
|
+
* @template TData - The data type returned by the mutation.
|
|
11
|
+
*
|
|
12
|
+
* @see {@link https://github.com/AcademyCity/rx-toolkit/blob/main/docs/query/api/command.md | Command API docs}
|
|
13
|
+
*/
|
|
14
|
+
export declare class Command<TArgs, TData> implements ICommand<TArgs, TData> {
|
|
15
|
+
private readonly _cache;
|
|
16
|
+
private readonly _lastEntry$;
|
|
17
|
+
private readonly _status$;
|
|
18
|
+
private readonly _queryFn;
|
|
19
|
+
readonly _key: string | undefined;
|
|
20
|
+
private readonly _linkManager;
|
|
21
|
+
private readonly _retentionTime;
|
|
22
|
+
private readonly _onCacheEntryAdded;
|
|
23
|
+
private readonly _onQueryStarted;
|
|
24
|
+
private _keyCounter;
|
|
25
|
+
constructor(config: ICommandConfig<TArgs, TData>);
|
|
26
|
+
/**
|
|
27
|
+
* Imperatively execute the mutation.
|
|
28
|
+
*
|
|
29
|
+
* Applies optimistic patches, runs `queryFn`, then commits/rolls-back
|
|
30
|
+
* patches and invalidates linked resources on success/failure.
|
|
31
|
+
*
|
|
32
|
+
* @param argsOrKeyed - Plain arguments or a {@link Keyed} wrapper.
|
|
33
|
+
* @param key - Optional cache-entry key. Auto-generated when omitted.
|
|
34
|
+
* @returns A promise that resolves with the mutation result.
|
|
35
|
+
*/
|
|
36
|
+
trigger(argsOrKeyed: Args<TArgs>, key?: string): Promise<TData>;
|
|
37
|
+
/**
|
|
38
|
+
* Synchronously retrieve a cache entry by key.
|
|
39
|
+
*
|
|
40
|
+
* @param key - The cache-entry key.
|
|
41
|
+
* @returns The matching {@link QueryCacheEntry}, or `null` if none exists.
|
|
42
|
+
*/
|
|
43
|
+
getEntry(key: string): QueryCacheEntry<TArgs, TData> | null;
|
|
44
|
+
/**
|
|
45
|
+
* Reactive variant of {@link getEntry}.
|
|
46
|
+
*
|
|
47
|
+
* Reads an internal signal so that callers in a reactive context
|
|
48
|
+
* (e.g. `computed`, `effect`) re-evaluate when the cache changes.
|
|
49
|
+
*
|
|
50
|
+
* @param key - The cache-entry key.
|
|
51
|
+
* @returns The matching {@link QueryCacheEntry}, or `null` if none exists.
|
|
52
|
+
*/
|
|
53
|
+
getEntry$(key: string): QueryCacheEntry<TArgs, TData> | null;
|
|
54
|
+
/**
|
|
55
|
+
* Create a reactive agent that observes this command's state.
|
|
56
|
+
*
|
|
57
|
+
* @param key - Optional key to bind the agent to a specific cache entry.
|
|
58
|
+
* @returns A new {@link ICommandAgent} instance.
|
|
59
|
+
*/
|
|
60
|
+
createAgent(key?: string): ICommandAgent<TArgs, TData>;
|
|
61
|
+
/** Clear all cache entries. Called by createApi.resetAll(). */
|
|
62
|
+
reset(): void;
|
|
63
|
+
private _generateKey;
|
|
64
|
+
private _toKeyed;
|
|
65
|
+
private _fireOnCacheEntryAdded;
|
|
66
|
+
private _fireOnQueryStarted;
|
|
67
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { Signal } from "../../../signals";
|
|
2
|
+
import { KEYED_BRAND } from "../../constants";
|
|
3
|
+
import { isKeyed } from "../../lib/toKeyed";
|
|
4
|
+
import { CacheMap } from "../cache/CacheMap";
|
|
5
|
+
import { QueryCacheEntry } from "../cache/QueryCacheEntry";
|
|
6
|
+
import { CacheEntryRemovedError } from "../errors";
|
|
7
|
+
import { CommandAgent } from "./CommandAgent";
|
|
8
|
+
import { LinkManager } from "./LinkManager";
|
|
9
|
+
// ==================== Command ====================
|
|
10
|
+
/**
|
|
11
|
+
* Abstraction for write operations (mutations).
|
|
12
|
+
*
|
|
13
|
+
* Manages cache entries, link-based optimistic/update patches on related resources,
|
|
14
|
+
* and lifecycle hooks (`onCacheEntryAdded`, `onQueryStarted`).
|
|
15
|
+
*
|
|
16
|
+
* @template TArgs - The argument type accepted by the mutation.
|
|
17
|
+
* @template TData - The data type returned by the mutation.
|
|
18
|
+
*
|
|
19
|
+
* @see {@link https://github.com/AcademyCity/rx-toolkit/blob/main/docs/query/api/command.md | Command API docs}
|
|
20
|
+
*/
|
|
21
|
+
export class Command {
|
|
22
|
+
_cache = new CacheMap();
|
|
23
|
+
_lastEntry$ = Signal.state(null, { isDisabled: true });
|
|
24
|
+
_status$ = Signal.state("idle", { isDisabled: true });
|
|
25
|
+
_queryFn;
|
|
26
|
+
_key;
|
|
27
|
+
_linkManager;
|
|
28
|
+
_retentionTime;
|
|
29
|
+
_onCacheEntryAdded;
|
|
30
|
+
_onQueryStarted;
|
|
31
|
+
_keyCounter = 0;
|
|
32
|
+
constructor(config) {
|
|
33
|
+
this._queryFn = config.queryFn;
|
|
34
|
+
this._key = config.key;
|
|
35
|
+
this._linkManager = new LinkManager(config.links);
|
|
36
|
+
this._retentionTime = config.retentionTime;
|
|
37
|
+
this._onCacheEntryAdded = config.onCacheEntryAdded;
|
|
38
|
+
this._onQueryStarted = config.onQueryStarted;
|
|
39
|
+
}
|
|
40
|
+
// ==================== Public API (ICommand) ====================
|
|
41
|
+
/**
|
|
42
|
+
* Imperatively execute the mutation.
|
|
43
|
+
*
|
|
44
|
+
* Applies optimistic patches, runs `queryFn`, then commits/rolls-back
|
|
45
|
+
* patches and invalidates linked resources on success/failure.
|
|
46
|
+
*
|
|
47
|
+
* @param argsOrKeyed - Plain arguments or a {@link Keyed} wrapper.
|
|
48
|
+
* @param key - Optional cache-entry key. Auto-generated when omitted.
|
|
49
|
+
* @returns A promise that resolves with the mutation result.
|
|
50
|
+
*/
|
|
51
|
+
trigger(argsOrKeyed, key) {
|
|
52
|
+
const keyed = this._toKeyed(argsOrKeyed, key);
|
|
53
|
+
const args = keyed.value;
|
|
54
|
+
const entryKey = keyed.key;
|
|
55
|
+
const linkManager = this._linkManager;
|
|
56
|
+
// 1. Apply optimistic patches (synchronous, before queryFn)
|
|
57
|
+
const patchHandles = linkManager.applyOptimisticPatches(args);
|
|
58
|
+
// 2. Set up result promise (resolved/rejected by link handlers after queryFn settles)
|
|
59
|
+
let resolveResult;
|
|
60
|
+
let rejectResult;
|
|
61
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
62
|
+
resolveResult = resolve;
|
|
63
|
+
rejectResult = reject;
|
|
64
|
+
});
|
|
65
|
+
// Clean up existing entry for the same key, if any
|
|
66
|
+
const existing = this._cache.get(entryKey);
|
|
67
|
+
if (existing) {
|
|
68
|
+
existing.complete();
|
|
69
|
+
}
|
|
70
|
+
// eslint-disable-next-line prefer-const -- assigned after constructor; closure reads it
|
|
71
|
+
let entry;
|
|
72
|
+
let initialQueryPromise = null;
|
|
73
|
+
let linksSettled = false;
|
|
74
|
+
const wrappedQueryFn = (keyedArgs, _signal) => {
|
|
75
|
+
const promise = this._queryFn(keyedArgs.value);
|
|
76
|
+
// Chain link handlers only on the initial trigger, not on QCE refresh
|
|
77
|
+
if (!linksSettled) {
|
|
78
|
+
promise.then((result) => {
|
|
79
|
+
linksSettled = true;
|
|
80
|
+
linkManager.settle(args, patchHandles, { status: "fulfilled", value: result });
|
|
81
|
+
resolveResult(result);
|
|
82
|
+
}, (error) => {
|
|
83
|
+
linksSettled = true;
|
|
84
|
+
linkManager.settle(args, patchHandles, { status: "rejected", reason: error });
|
|
85
|
+
rejectResult(error);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Lifecycle: onQueryStarted
|
|
89
|
+
if (entry) {
|
|
90
|
+
this._fireOnQueryStarted(entry, keyedArgs.value, promise);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
initialQueryPromise = promise;
|
|
94
|
+
}
|
|
95
|
+
return promise;
|
|
96
|
+
};
|
|
97
|
+
// 4. Create QueryCacheEntry — auto-executes wrappedQueryFn in constructor
|
|
98
|
+
entry = new QueryCacheEntry({
|
|
99
|
+
queryFn: wrappedQueryFn,
|
|
100
|
+
retentionTime: this._retentionTime,
|
|
101
|
+
keyedArgs: keyed,
|
|
102
|
+
resourceKey: this._key,
|
|
103
|
+
});
|
|
104
|
+
// Register in cache
|
|
105
|
+
this._cache.set(entryKey, entry);
|
|
106
|
+
this._status$.set("running");
|
|
107
|
+
this._lastEntry$.set(entry);
|
|
108
|
+
// Cleanup: remove entry from cache when it completes
|
|
109
|
+
entry.completed$.subscribe(() => {
|
|
110
|
+
// Guard: only remove if THIS entry is still the current one for the key
|
|
111
|
+
if (this._cache.get(entryKey) === entry) {
|
|
112
|
+
this._cache.delete(entryKey);
|
|
113
|
+
if (this._cache.size === 0)
|
|
114
|
+
this._status$.set("idle");
|
|
115
|
+
if (this._lastEntry$() === entry) {
|
|
116
|
+
this._lastEntry$.set(null);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// Fire onCacheEntryAdded lifecycle hook
|
|
121
|
+
this._fireOnCacheEntryAdded(entry, keyed);
|
|
122
|
+
// Fire onQueryStarted for the initial query (deferred from constructor)
|
|
123
|
+
if (initialQueryPromise) {
|
|
124
|
+
this._fireOnQueryStarted(entry, keyed.value, initialQueryPromise);
|
|
125
|
+
}
|
|
126
|
+
return resultPromise;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Synchronously retrieve a cache entry by key.
|
|
130
|
+
*
|
|
131
|
+
* @param key - The cache-entry key.
|
|
132
|
+
* @returns The matching {@link QueryCacheEntry}, or `null` if none exists.
|
|
133
|
+
*/
|
|
134
|
+
getEntry(key) {
|
|
135
|
+
return this._cache.get(key) ?? null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Reactive variant of {@link getEntry}.
|
|
139
|
+
*
|
|
140
|
+
* Reads an internal signal so that callers in a reactive context
|
|
141
|
+
* (e.g. `computed`, `effect`) re-evaluate when the cache changes.
|
|
142
|
+
*
|
|
143
|
+
* @param key - The cache-entry key.
|
|
144
|
+
* @returns The matching {@link QueryCacheEntry}, or `null` if none exists.
|
|
145
|
+
*/
|
|
146
|
+
getEntry$(key) {
|
|
147
|
+
let entry = null;
|
|
148
|
+
const signal$ = Signal.compute(() => {
|
|
149
|
+
const status = this._status$();
|
|
150
|
+
if (status === "idle") {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
// Fast path: already found in a previous evaluation
|
|
154
|
+
if (entry) {
|
|
155
|
+
return entry;
|
|
156
|
+
}
|
|
157
|
+
const lastEntry = this._lastEntry$();
|
|
158
|
+
if (lastEntry?.keyedArgs.key === key) {
|
|
159
|
+
entry = lastEntry;
|
|
160
|
+
return entry;
|
|
161
|
+
}
|
|
162
|
+
return this._cache.get(key) ?? null;
|
|
163
|
+
}, { isDisabled: true });
|
|
164
|
+
return signal$();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Create a reactive agent that observes this command's state.
|
|
168
|
+
*
|
|
169
|
+
* @param key - Optional key to bind the agent to a specific cache entry.
|
|
170
|
+
* @returns A new {@link ICommandAgent} instance.
|
|
171
|
+
*/
|
|
172
|
+
createAgent(key) {
|
|
173
|
+
return new CommandAgent(this, key);
|
|
174
|
+
}
|
|
175
|
+
/** Clear all cache entries. Called by createApi.resetAll(). */
|
|
176
|
+
reset() {
|
|
177
|
+
const entries = [...this._cache.values()];
|
|
178
|
+
this._cache.clear();
|
|
179
|
+
this._status$.set("idle");
|
|
180
|
+
this._lastEntry$.set(null);
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
entry.complete();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// ==================== Private — Key Generation ====================
|
|
186
|
+
_generateKey() {
|
|
187
|
+
return `${Date.now()}-${this._keyCounter++}`;
|
|
188
|
+
}
|
|
189
|
+
_toKeyed(args, key) {
|
|
190
|
+
if (isKeyed(args)) {
|
|
191
|
+
return args;
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
value: args,
|
|
195
|
+
key: key ?? this._generateKey(),
|
|
196
|
+
[KEYED_BRAND]: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// ==================== Private — Lifecycle Hooks ====================
|
|
200
|
+
_fireOnCacheEntryAdded(entry, keyed) {
|
|
201
|
+
if (!this._onCacheEntryAdded)
|
|
202
|
+
return;
|
|
203
|
+
let resolveRemoved;
|
|
204
|
+
const $cacheEntryRemoved = new Promise((resolve) => {
|
|
205
|
+
resolveRemoved = resolve;
|
|
206
|
+
});
|
|
207
|
+
const $cacheDataLoaded = new Promise((resolve, reject) => {
|
|
208
|
+
const sub = entry.state$.obs.subscribe((machine) => {
|
|
209
|
+
if (machine.state.status === "success" || machine.state.status === "refreshing") {
|
|
210
|
+
resolve(machine.state.data);
|
|
211
|
+
sub.unsubscribe();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
entry.completed$.subscribe(() => {
|
|
215
|
+
sub.unsubscribe();
|
|
216
|
+
reject(new CacheEntryRemovedError("data loaded"));
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
entry.completed$.subscribe(() => {
|
|
220
|
+
resolveRemoved();
|
|
221
|
+
});
|
|
222
|
+
const ctx = {
|
|
223
|
+
entry,
|
|
224
|
+
$cacheDataLoaded,
|
|
225
|
+
$cacheEntryRemoved,
|
|
226
|
+
};
|
|
227
|
+
try {
|
|
228
|
+
const result = this._onCacheEntryAdded(keyed.value, ctx);
|
|
229
|
+
// Hook may be async — suppress unhandled rejection
|
|
230
|
+
void Promise.resolve(result).catch(() => { });
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// Lifecycle errors are suppressed (per docs)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
_fireOnQueryStarted(entry, args, queryPromise) {
|
|
237
|
+
if (!this._onQueryStarted)
|
|
238
|
+
return;
|
|
239
|
+
const $queryFulfilled = queryPromise.then((data) => ({ data }));
|
|
240
|
+
const ctx = {
|
|
241
|
+
entry,
|
|
242
|
+
$queryFulfilled,
|
|
243
|
+
};
|
|
244
|
+
try {
|
|
245
|
+
const result = this._onQueryStarted(args, ctx);
|
|
246
|
+
// Hook may be async — suppress unhandled rejection
|
|
247
|
+
void Promise.resolve(result).catch(() => { });
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Lifecycle errors are suppressed (per docs)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Args, ICommandAgent, IQueryCacheEntry, TCommandAgentState } from "../../../query/types";
|
|
2
|
+
import type { ComputeFn } from "../../../signals/types";
|
|
3
|
+
export interface ICommandForAgent<TArgs, TData> {
|
|
4
|
+
trigger(args: Args<TArgs>, key?: string): Promise<TData>;
|
|
5
|
+
getEntry$(key: string): IQueryCacheEntry<TArgs, TData> | null;
|
|
6
|
+
}
|
|
7
|
+
export declare class CommandAgent<TArgs, TData> implements ICommandAgent<TArgs, TData> {
|
|
8
|
+
private readonly _command;
|
|
9
|
+
private readonly _tracking$;
|
|
10
|
+
readonly state$: ComputeFn<TCommandAgentState<TArgs, TData>>;
|
|
11
|
+
constructor(command: ICommandForAgent<TArgs, TData>, key?: string);
|
|
12
|
+
trigger(args: Args<TArgs>, key?: string): Promise<TData>;
|
|
13
|
+
setKey(key: string): void;
|
|
14
|
+
private _observeKey;
|
|
15
|
+
private _deriveState;
|
|
16
|
+
private _createIdleState;
|
|
17
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Signal } from "../../../signals";
|
|
2
|
+
export class CommandAgent {
|
|
3
|
+
_command;
|
|
4
|
+
_tracking$;
|
|
5
|
+
state$;
|
|
6
|
+
constructor(command, key) {
|
|
7
|
+
this._command = command;
|
|
8
|
+
this._tracking$ = Signal.state(null, { isDisabled: true });
|
|
9
|
+
this.state$ = Signal.compute(() => {
|
|
10
|
+
const tracking = this._tracking$();
|
|
11
|
+
if (!tracking)
|
|
12
|
+
return this._createIdleState();
|
|
13
|
+
const entry = tracking.current$();
|
|
14
|
+
if (!entry)
|
|
15
|
+
return this._createIdleState();
|
|
16
|
+
const machineState = entry.state$().state;
|
|
17
|
+
return this._deriveState(entry, machineState);
|
|
18
|
+
}, { isDisabled: true });
|
|
19
|
+
if (key != null) {
|
|
20
|
+
this.setKey(key);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async trigger(args, key) {
|
|
24
|
+
const result = this._command.trigger(args, key);
|
|
25
|
+
if (key != null) {
|
|
26
|
+
this._observeKey(key);
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
setKey(key) {
|
|
31
|
+
this._observeKey(key);
|
|
32
|
+
}
|
|
33
|
+
// ==================== Private ====================
|
|
34
|
+
_observeKey(key) {
|
|
35
|
+
const tracking = this._tracking$.peek();
|
|
36
|
+
if (tracking && tracking.key === key)
|
|
37
|
+
return;
|
|
38
|
+
const current$ = Signal.compute(() => this._command.getEntry$(key), { isDisabled: true });
|
|
39
|
+
this._tracking$.set({ key, current$ });
|
|
40
|
+
}
|
|
41
|
+
_deriveState(entry, machineState) {
|
|
42
|
+
// Command agent uses a simplified status mapping:
|
|
43
|
+
// refreshing / refresh-error are not applicable to commands → map to pending.
|
|
44
|
+
const machineStatus = machineState.status;
|
|
45
|
+
const status = machineStatus === "refreshing" || machineStatus === "refresh-error" ? "pending" : machineStatus;
|
|
46
|
+
return {
|
|
47
|
+
status,
|
|
48
|
+
data: machineState.data,
|
|
49
|
+
error: machineState.error,
|
|
50
|
+
args: machineState.args,
|
|
51
|
+
isLoading: status === "pending",
|
|
52
|
+
isSuccess: status === "success",
|
|
53
|
+
isError: status === "error",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
_createIdleState() {
|
|
57
|
+
return {
|
|
58
|
+
status: "idle",
|
|
59
|
+
data: null,
|
|
60
|
+
error: null,
|
|
61
|
+
args: null,
|
|
62
|
+
isLoading: false,
|
|
63
|
+
isSuccess: false,
|
|
64
|
+
isError: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IPatchHandle, TLinkConfig } from "../../../query/types";
|
|
2
|
+
/**
|
|
3
|
+
* Encapsulates link-based patching and invalidation logic for a {@link Command}.
|
|
4
|
+
*
|
|
5
|
+
* Responsible for:
|
|
6
|
+
* - Applying optimistic patches before the mutation runs.
|
|
7
|
+
* - Applying update patches after successful mutation.
|
|
8
|
+
* - Invalidating / refreshing linked resources.
|
|
9
|
+
*
|
|
10
|
+
* @template TArgs - The argument type of the owning Command.
|
|
11
|
+
* @template TData - The data type returned by the owning Command.
|
|
12
|
+
*/
|
|
13
|
+
export declare class LinkManager<TArgs, TData> {
|
|
14
|
+
private readonly _links;
|
|
15
|
+
constructor(_links: TLinkConfig<TArgs, TData, any, any>[]);
|
|
16
|
+
applyOptimisticPatches(args: TArgs): IPatchHandle[];
|
|
17
|
+
applyUpdatePatches(args: TArgs, result: TData): void;
|
|
18
|
+
invalidateResources(args: TArgs): void;
|
|
19
|
+
/**
|
|
20
|
+
* Handle the settled result of a mutation: commit or rollback optimistic
|
|
21
|
+
* patches, apply update patches, and invalidate linked resources.
|
|
22
|
+
*/
|
|
23
|
+
settle(args: TArgs, patchHandles: IPatchHandle[], result: PromiseSettledResult<TData>): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// ==================== LinkManager ====================
|
|
2
|
+
/**
|
|
3
|
+
* Encapsulates link-based patching and invalidation logic for a {@link Command}.
|
|
4
|
+
*
|
|
5
|
+
* Responsible for:
|
|
6
|
+
* - Applying optimistic patches before the mutation runs.
|
|
7
|
+
* - Applying update patches after successful mutation.
|
|
8
|
+
* - Invalidating / refreshing linked resources.
|
|
9
|
+
*
|
|
10
|
+
* @template TArgs - The argument type of the owning Command.
|
|
11
|
+
* @template TData - The data type returned by the owning Command.
|
|
12
|
+
*/
|
|
13
|
+
export class LinkManager {
|
|
14
|
+
_links;
|
|
15
|
+
constructor(_links) {
|
|
16
|
+
this._links = _links;
|
|
17
|
+
}
|
|
18
|
+
applyOptimisticPatches(args) {
|
|
19
|
+
const handles = [];
|
|
20
|
+
for (const link of this._links) {
|
|
21
|
+
if (!link.optimisticUpdate)
|
|
22
|
+
continue;
|
|
23
|
+
const forwardedArgs = link.forwardArgs(args);
|
|
24
|
+
const entry = link.resource.getEntry(forwardedArgs);
|
|
25
|
+
const handle = entry?.createPatch((draft) => {
|
|
26
|
+
link.optimisticUpdate(draft, args);
|
|
27
|
+
});
|
|
28
|
+
if (handle)
|
|
29
|
+
handles.push(handle);
|
|
30
|
+
}
|
|
31
|
+
return handles;
|
|
32
|
+
}
|
|
33
|
+
applyUpdatePatches(args, result) {
|
|
34
|
+
for (const link of this._links) {
|
|
35
|
+
if (!link.update)
|
|
36
|
+
continue;
|
|
37
|
+
const forwardedArgs = link.forwardArgs(args);
|
|
38
|
+
const entry = link.resource.getEntry(forwardedArgs);
|
|
39
|
+
const handle = entry?.createPatch((draft) => {
|
|
40
|
+
link.update(draft, args, result);
|
|
41
|
+
});
|
|
42
|
+
if (handle)
|
|
43
|
+
handle.commit();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
invalidateResources(args) {
|
|
47
|
+
for (const link of this._links) {
|
|
48
|
+
if (!link.invalidate)
|
|
49
|
+
continue;
|
|
50
|
+
const forwardedArgs = link.forwardArgs(args);
|
|
51
|
+
const resource = link.resource;
|
|
52
|
+
resource.refresh(forwardedArgs);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Handle the settled result of a mutation: commit or rollback optimistic
|
|
57
|
+
* patches, apply update patches, and invalidate linked resources.
|
|
58
|
+
*/
|
|
59
|
+
settle(args, patchHandles, result) {
|
|
60
|
+
if (result.status === "fulfilled") {
|
|
61
|
+
this.applyUpdatePatches(args, result.value);
|
|
62
|
+
for (const h of patchHandles)
|
|
63
|
+
h.commit();
|
|
64
|
+
this.invalidateResources(args);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
for (const h of patchHandles)
|
|
68
|
+
h.abort();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when a Machine method requires state that doesn't exist
|
|
3
|
+
* (e.g. createPatch on a state without data, finishPatch without patchState).
|
|
4
|
+
*/
|
|
5
|
+
export declare class MachineStateError extends Error {
|
|
6
|
+
readonly name = "MachineStateError";
|
|
7
|
+
constructor(method: string, detail: string);
|
|
8
|
+
}
|