@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.
Files changed (302) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +22 -13
  3. package/dist/common/devtools/reduxDevtools.js +17 -2
  4. package/dist/common/react/index.d.ts +1 -0
  5. package/dist/common/react/index.js +1 -0
  6. package/dist/common/react/useIsomorphicLayoutEffect.d.ts +17 -0
  7. package/dist/common/react/useIsomorphicLayoutEffect.js +17 -0
  8. package/dist/index.d.ts +1 -2
  9. package/dist/index.js +1 -2
  10. package/dist/query/api/createApi.d.ts +4 -0
  11. package/dist/query/api/createApi.js +9 -0
  12. package/dist/query/api/index.d.ts +1 -0
  13. package/dist/query/api/index.js +1 -0
  14. package/dist/query/constants.d.ts +12 -0
  15. package/dist/query/constants.js +15 -0
  16. package/dist/query/core/api/Api.d.ts +20 -0
  17. package/dist/query/core/api/Api.js +129 -0
  18. package/dist/query/core/api/constants.d.ts +2 -0
  19. package/dist/query/core/api/constants.js +3 -0
  20. package/dist/query/core/api/index.d.ts +4 -0
  21. package/dist/query/core/api/index.js +4 -0
  22. package/dist/query/core/api/mergeHooks.d.ts +2 -0
  23. package/dist/query/core/api/mergeHooks.js +26 -0
  24. package/dist/query/core/api/normalizeLinks.d.ts +2 -0
  25. package/dist/query/core/api/normalizeLinks.js +11 -0
  26. package/dist/query/core/cache/CacheEntry.d.ts +21 -0
  27. package/dist/query/core/cache/CacheEntry.js +54 -0
  28. package/dist/query/core/cache/CacheMap.d.ts +19 -0
  29. package/dist/query/core/cache/CacheMap.js +32 -0
  30. package/dist/query/core/cache/QueryCacheEntry.d.ts +21 -0
  31. package/dist/query/core/cache/QueryCacheEntry.js +136 -0
  32. package/dist/query/core/cache/index.d.ts +3 -0
  33. package/dist/query/core/cache/index.js +3 -0
  34. package/dist/query/core/command/Command.d.ts +67 -0
  35. package/dist/query/core/command/Command.js +253 -0
  36. package/dist/query/core/command/CommandAgent.d.ts +17 -0
  37. package/dist/query/core/command/CommandAgent.js +67 -0
  38. package/dist/query/core/command/LinkManager.d.ts +24 -0
  39. package/dist/query/core/command/LinkManager.js +71 -0
  40. package/dist/query/core/command/index.d.ts +2 -0
  41. package/dist/query/core/command/index.js +2 -0
  42. package/dist/query/core/errors/CacheEntryRemovedError.d.ts +7 -0
  43. package/dist/query/core/errors/CacheEntryRemovedError.js +9 -0
  44. package/dist/query/core/errors/MachineStateError.d.ts +8 -0
  45. package/dist/query/core/errors/MachineStateError.js +10 -0
  46. package/dist/query/core/errors/MachineTransitionError.d.ts +7 -0
  47. package/dist/query/core/errors/MachineTransitionError.js +9 -0
  48. package/dist/query/core/errors/index.d.ts +3 -0
  49. package/dist/query/core/errors/index.js +3 -0
  50. package/dist/query/core/index.d.ts +8 -0
  51. package/dist/query/core/index.js +8 -0
  52. package/dist/query/core/machine/Machine.d.ts +21 -0
  53. package/dist/query/core/machine/Machine.js +42 -0
  54. package/dist/query/core/machine/MachineBase.d.ts +31 -0
  55. package/dist/query/core/machine/MachineBase.js +176 -0
  56. package/dist/query/core/machine/MachineError.d.ts +10 -0
  57. package/dist/query/core/machine/MachineError.js +19 -0
  58. package/dist/query/core/machine/MachinePending.d.ts +13 -0
  59. package/dist/query/core/machine/MachinePending.js +32 -0
  60. package/dist/query/core/machine/MachineRefreshError.d.ts +12 -0
  61. package/dist/query/core/machine/MachineRefreshError.js +23 -0
  62. package/dist/query/core/machine/MachineRefreshing.d.ts +15 -0
  63. package/dist/query/core/machine/MachineRefreshing.js +43 -0
  64. package/dist/query/core/machine/MachineSuccess.d.ts +12 -0
  65. package/dist/query/core/machine/MachineSuccess.js +23 -0
  66. package/dist/query/core/machine/MachineWithData.d.ts +25 -0
  67. package/dist/query/core/machine/MachineWithData.js +73 -0
  68. package/dist/query/core/machine/index.d.ts +9 -0
  69. package/dist/query/core/machine/index.js +9 -0
  70. package/dist/query/core/machine/machine-helpers.d.ts +9 -0
  71. package/dist/query/core/machine/machine-helpers.js +80 -0
  72. package/dist/query/core/patcher/Patcher.d.ts +30 -0
  73. package/dist/query/core/patcher/Patcher.js +122 -0
  74. package/dist/query/core/patcher/index.d.ts +1 -0
  75. package/dist/query/core/patcher/index.js +1 -0
  76. package/dist/query/core/resource/Resource.d.ts +105 -0
  77. package/dist/query/core/resource/Resource.js +340 -0
  78. package/dist/query/core/resource/ResourceAgent.d.ts +37 -0
  79. package/dist/query/core/resource/ResourceAgent.js +179 -0
  80. package/dist/query/core/resource/index.d.ts +2 -0
  81. package/dist/query/core/resource/index.js +2 -0
  82. package/dist/query/core/snapshoter/Snapshoter.d.ts +22 -0
  83. package/dist/query/core/snapshoter/Snapshoter.js +78 -0
  84. package/dist/query/core/snapshoter/index.d.ts +2 -0
  85. package/dist/query/core/snapshoter/index.js +1 -0
  86. package/dist/query/core/syncer/Syncer.d.ts +32 -0
  87. package/dist/query/core/syncer/Syncer.js +85 -0
  88. package/dist/query/core/syncer/index.d.ts +2 -0
  89. package/dist/query/core/syncer/index.js +1 -0
  90. package/dist/query/index.d.ts +5 -10
  91. package/dist/query/index.js +5 -13
  92. package/dist/query/lib/broadcastSyncDriver.d.ts +5 -0
  93. package/dist/query/lib/broadcastSyncDriver.js +46 -0
  94. package/dist/query/lib/index.d.ts +3 -0
  95. package/dist/query/lib/index.js +3 -0
  96. package/dist/{query-v2 → query}/lib/stableStringify.js +5 -3
  97. package/dist/query/lib/toKeyed.d.ts +11 -0
  98. package/dist/query/lib/toKeyed.js +18 -0
  99. package/dist/query/react/ReactHooksPlugin.d.ts +31 -0
  100. package/dist/query/react/ReactHooksPlugin.js +21 -0
  101. package/dist/query/react/index.d.ts +3 -0
  102. package/dist/query/react/index.js +3 -0
  103. package/dist/query/react/useCommand.d.ts +2 -0
  104. package/dist/query/react/useCommand.js +14 -0
  105. package/dist/query/react/useResource.d.ts +2 -0
  106. package/dist/query/react/useResource.js +16 -0
  107. package/dist/query/types/api.d.ts +39 -0
  108. package/dist/query/types/cache.d.ts +51 -0
  109. package/dist/query/types/command.d.ts +52 -0
  110. package/dist/query/types/common.d.ts +65 -0
  111. package/dist/query/types/index.d.ts +8 -4
  112. package/dist/query/types/index.js +8 -5
  113. package/dist/query/types/plugin-hkt.d.ts +64 -0
  114. package/dist/query/types/resource.d.ts +49 -0
  115. package/dist/query/types/snapshot.d.ts +27 -0
  116. package/dist/query/types/snapshot.js +2 -0
  117. package/dist/query/types/state.d.ts +24 -0
  118. package/dist/signals/base/ComputeCache.js +1 -1
  119. package/dist/signals/base/Devtools.js +2 -6
  120. package/dist/signals/signals/Computed.d.ts +1 -0
  121. package/dist/signals/signals/Computed.js +5 -1
  122. package/dist/signals/signals/Effect.d.ts +0 -4
  123. package/dist/signals/signals/Effect.js +0 -6
  124. package/dist/signals/signals/LocalState.d.ts +1 -10
  125. package/dist/signals/signals/LocalState.js +0 -12
  126. package/dist/signals/signals/Signal.d.ts +2 -8
  127. package/dist/signals/signals/Signal.js +1 -10
  128. package/dist/signals/signals/State.d.ts +2 -1
  129. package/dist/signals/signals/State.js +9 -0
  130. package/dist/signals/types/SignalOptions.d.ts +0 -2
  131. package/dist/signals/types/normalizeSignalOptions.js +0 -3
  132. package/dist/signals/types/signals.types.d.ts +3 -1
  133. package/docs/CHANGELOG.md +48 -2
  134. package/docs/migrations/0.6.0.md +224 -0
  135. package/docs/query/README.md +52 -562
  136. package/docs/query/api/README.md +59 -0
  137. package/docs/query/api/_CacheEntry.md +39 -0
  138. package/docs/query/api/_CacheMap.md +30 -0
  139. package/docs/query/api/_QueryCacheEntry.md +81 -0
  140. package/docs/query/api/command-agent.md +60 -0
  141. package/docs/query/api/command.md +76 -0
  142. package/docs/query/api/resource-agent.md +68 -0
  143. package/docs/query/api/resource.md +77 -0
  144. package/docs/query/concepts/agent.md +70 -0
  145. package/docs/query/concepts/architecture.md +139 -0
  146. package/docs/query/concepts/cache.md +81 -0
  147. package/docs/query/concepts/dataflows.md +473 -0
  148. package/docs/query/concepts/keyed.md +42 -0
  149. package/docs/query/concepts/machine.md +106 -0
  150. package/docs/query/concepts/patching.md +85 -0
  151. package/docs/query/usage/broadcast.md +188 -0
  152. package/docs/query/usage/command.md +203 -0
  153. package/docs/query/usage/lifecycle.md +114 -0
  154. package/docs/query/usage/links.md +125 -0
  155. package/docs/query/usage/plugins.md +96 -0
  156. package/docs/query/usage/resource.md +206 -0
  157. package/docs/query/usage/snapshot.md +80 -0
  158. package/docs/usage/react/README.md +45 -91
  159. package/package.json +5 -7
  160. package/dist/query/SKIP_TOKEN.d.ts +0 -1
  161. package/dist/query/SKIP_TOKEN.js +0 -1
  162. package/dist/query/api/createCommand.d.ts +0 -21
  163. package/dist/query/api/createCommand.js +0 -20
  164. package/dist/query/api/createOperation.d.ts +0 -5
  165. package/dist/query/api/createOperation.js +0 -6
  166. package/dist/query/api/createResource.d.ts +0 -3
  167. package/dist/query/api/createResource.js +0 -2
  168. package/dist/query/api/createResourceDuplicator.d.ts +0 -4
  169. package/dist/query/api/createResourceDuplicator.js +0 -2
  170. package/dist/query/api/resetAllQueriesCache.d.ts +0 -1
  171. package/dist/query/api/resetAllQueriesCache.js +0 -4
  172. package/dist/query/core/Command/Command.d.ts +0 -35
  173. package/dist/query/core/Command/Command.js +0 -210
  174. package/dist/query/core/Command/CommandAgent.d.ts +0 -19
  175. package/dist/query/core/Command/CommandAgent.js +0 -54
  176. package/dist/query/core/Command/index.d.ts +0 -2
  177. package/dist/query/core/Command/index.js +0 -2
  178. package/dist/query/core/Operation/Operation.d.ts +0 -8
  179. package/dist/query/core/Operation/Operation.js +0 -4
  180. package/dist/query/core/Operation/OperationAgent.d.ts +0 -4
  181. package/dist/query/core/Operation/OperationAgent.js +0 -4
  182. package/dist/query/core/QueriesCache.d.ts +0 -9
  183. package/dist/query/core/QueriesCache.js +0 -28
  184. package/dist/query/core/QueriesLifetimeHooks.d.ts +0 -22
  185. package/dist/query/core/QueriesLifetimeHooks.js +0 -86
  186. package/dist/query/core/ResetAllQueriesSignal.d.ts +0 -6
  187. package/dist/query/core/ResetAllQueriesSignal.js +0 -11
  188. package/dist/query/core/Resource/Resource.d.ts +0 -51
  189. package/dist/query/core/Resource/Resource.js +0 -232
  190. package/dist/query/core/Resource/ResourceAgent.d.ts +0 -35
  191. package/dist/query/core/Resource/ResourceAgent.js +0 -110
  192. package/dist/query/core/Resource/ResourceDuplicator.d.ts +0 -73
  193. package/dist/query/core/Resource/ResourceDuplicator.js +0 -227
  194. package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +0 -35
  195. package/dist/query/core/Resource/ResourceDuplicatorAgent.js +0 -110
  196. package/dist/query/core/Resource/ResourceRef.d.ts +0 -16
  197. package/dist/query/core/Resource/ResourceRef.js +0 -136
  198. package/dist/query/lib/IndirectMap.d.ts +0 -19
  199. package/dist/query/lib/IndirectMap.js +0 -88
  200. package/dist/query/lib/ReactiveCache.d.ts +0 -62
  201. package/dist/query/lib/ReactiveCache.js +0 -80
  202. package/dist/query/react/useCommandAgent.d.ts +0 -24
  203. package/dist/query/react/useCommandAgent.js +0 -39
  204. package/dist/query/react/useOperationAgent.d.ts +0 -6
  205. package/dist/query/react/useOperationAgent.js +0 -6
  206. package/dist/query/react/useResourceAgent.d.ts +0 -6
  207. package/dist/query/react/useResourceAgent.js +0 -31
  208. package/dist/query/react/useResourceRef.d.ts +0 -5
  209. package/dist/query/react/useResourceRef.js +0 -13
  210. package/dist/query/types/Command.types.d.ts +0 -154
  211. package/dist/query/types/Command.types.js +0 -1
  212. package/dist/query/types/Operation.types.d.ts +0 -13
  213. package/dist/query/types/Operation.types.js +0 -1
  214. package/dist/query/types/Resource.types.d.ts +0 -129
  215. package/dist/query/types/Resource.types.js +0 -1
  216. package/dist/query/types/shared.types.d.ts +0 -26
  217. package/dist/query/types/shared.types.js +0 -1
  218. package/dist/query-v2/api/createApi.d.ts +0 -10
  219. package/dist/query-v2/api/createApi.js +0 -83
  220. package/dist/query-v2/core/common/CacheEntry.d.ts +0 -29
  221. package/dist/query-v2/core/common/CacheEntry.js +0 -71
  222. package/dist/query-v2/core/common/CacheMap.d.ts +0 -38
  223. package/dist/query-v2/core/common/CacheMap.js +0 -127
  224. package/dist/query-v2/core/common/LifecycleHooks.d.ts +0 -22
  225. package/dist/query-v2/core/common/LifecycleHooks.js +0 -104
  226. package/dist/query-v2/core/common/index.d.ts +0 -3
  227. package/dist/query-v2/core/common/index.js +0 -3
  228. package/dist/query-v2/core/index.d.ts +0 -3
  229. package/dist/query-v2/core/index.js +0 -3
  230. package/dist/query-v2/core/machines/Machine.d.ts +0 -14
  231. package/dist/query-v2/core/machines/Machine.js +0 -33
  232. package/dist/query-v2/core/machines/MachineError.d.ts +0 -11
  233. package/dist/query-v2/core/machines/MachineError.js +0 -26
  234. package/dist/query-v2/core/machines/MachineIdle.d.ts +0 -8
  235. package/dist/query-v2/core/machines/MachineIdle.js +0 -19
  236. package/dist/query-v2/core/machines/MachinePending.d.ts +0 -12
  237. package/dist/query-v2/core/machines/MachinePending.js +0 -29
  238. package/dist/query-v2/core/machines/MachineRefreshing.d.ts +0 -14
  239. package/dist/query-v2/core/machines/MachineRefreshing.js +0 -46
  240. package/dist/query-v2/core/machines/MachineSuccess.d.ts +0 -16
  241. package/dist/query-v2/core/machines/MachineSuccess.js +0 -42
  242. package/dist/query-v2/core/machines/MachineWithData.d.ts +0 -18
  243. package/dist/query-v2/core/machines/MachineWithData.js +0 -40
  244. package/dist/query-v2/core/machines/Patcher.d.ts +0 -20
  245. package/dist/query-v2/core/machines/Patcher.js +0 -104
  246. package/dist/query-v2/core/machines/index.d.ts +0 -8
  247. package/dist/query-v2/core/machines/index.js +0 -8
  248. package/dist/query-v2/core/resource/ResourceV2.d.ts +0 -120
  249. package/dist/query-v2/core/resource/ResourceV2.js +0 -464
  250. package/dist/query-v2/core/resource/ResourceV2Agent.d.ts +0 -26
  251. package/dist/query-v2/core/resource/ResourceV2Agent.js +0 -132
  252. package/dist/query-v2/core/resource/index.d.ts +0 -2
  253. package/dist/query-v2/core/resource/index.js +0 -2
  254. package/dist/query-v2/index.d.ts +0 -11
  255. package/dist/query-v2/index.js +0 -17
  256. package/dist/query-v2/lib/NO_VALUE.d.ts +0 -2
  257. package/dist/query-v2/lib/NO_VALUE.js +0 -1
  258. package/dist/query-v2/lib/SKIP_TOKEN.d.ts +0 -2
  259. package/dist/query-v2/lib/SKIP_TOKEN.js +0 -1
  260. package/dist/query-v2/lib/index.d.ts +0 -4
  261. package/dist/query-v2/lib/index.js +0 -3
  262. package/dist/query-v2/plugins/ReactHooksPlugin.d.ts +0 -25
  263. package/dist/query-v2/plugins/ReactHooksPlugin.js +0 -19
  264. package/dist/query-v2/plugins/types.d.ts +0 -1
  265. package/dist/query-v2/react/__tests__/helpers.d.ts +0 -12
  266. package/dist/query-v2/react/__tests__/helpers.js +0 -33
  267. package/dist/query-v2/react/index.d.ts +0 -2
  268. package/dist/query-v2/react/index.js +0 -2
  269. package/dist/query-v2/react/useResourceV2Agent.d.ts +0 -12
  270. package/dist/query-v2/react/useResourceV2Agent.js +0 -36
  271. package/dist/query-v2/react/useResourceV2Ref.d.ts +0 -12
  272. package/dist/query-v2/react/useResourceV2Ref.js +0 -57
  273. package/dist/query-v2/snapshot/Snapshot.d.ts +0 -13
  274. package/dist/query-v2/snapshot/Snapshot.js +0 -76
  275. package/dist/query-v2/types/agent.types.d.ts +0 -54
  276. package/dist/query-v2/types/api.types.d.ts +0 -22
  277. package/dist/query-v2/types/cache.types.d.ts +0 -37
  278. package/dist/query-v2/types/index.d.ts +0 -9
  279. package/dist/query-v2/types/index.js +0 -9
  280. package/dist/query-v2/types/lifecycle.types.d.ts +0 -25
  281. package/dist/query-v2/types/machine.types.d.ts +0 -67
  282. package/dist/query-v2/types/plugin.types.d.ts +0 -38
  283. package/dist/query-v2/types/resource.types.d.ts +0 -35
  284. package/dist/query-v2/types/resource.types.js +0 -1
  285. package/dist/query-v2/types/shared.types.d.ts +0 -20
  286. package/dist/query-v2/types/shared.types.js +0 -1
  287. package/dist/query-v2/types/snapshot.types.d.ts +0 -21
  288. package/dist/query-v2/types/snapshot.types.js +0 -1
  289. package/docs/contributing/query-v2/README.md +0 -379
  290. package/docs/migrations/query-v2.md +0 -171
  291. package/docs/query-v2/README.md +0 -280
  292. package/docs/query-v2/api-reference.md +0 -235
  293. package/docs/query-v2/optimistic-updates.md +0 -148
  294. package/docs/query-v2/ssr.md +0 -130
  295. /package/dist/{query-v2 → query}/lib/stableStringify.d.ts +0 -0
  296. /package/dist/{query-v2/plugins/types.js → query/types/api.js} +0 -0
  297. /package/dist/{query-v2/types/agent.types.js → query/types/cache.js} +0 -0
  298. /package/dist/{query-v2/types/api.types.js → query/types/command.js} +0 -0
  299. /package/dist/{query-v2/types/cache.types.js → query/types/common.js} +0 -0
  300. /package/dist/{query-v2/types/lifecycle.types.js → query/types/plugin-hkt.js} +0 -0
  301. /package/dist/{query-v2/types/machine.types.js → query/types/resource.js} +0 -0
  302. /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,3 @@
1
+ export * from "./CacheEntry";
2
+ export * from "./CacheMap";
3
+ export * from "./QueryCacheEntry";
@@ -0,0 +1,3 @@
1
+ export * from "./CacheEntry";
2
+ export * from "./CacheMap";
3
+ export * from "./QueryCacheEntry";
@@ -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,2 @@
1
+ export * from "./Command";
2
+ export * from "./CommandAgent";
@@ -0,0 +1,2 @@
1
+ export * from "./Command";
2
+ export * from "./CommandAgent";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Thrown when a cache entry is removed before an async operation settles.
3
+ */
4
+ export declare class CacheEntryRemovedError extends Error {
5
+ readonly name = "CacheEntryRemovedError";
6
+ constructor(detail: string);
7
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Thrown when a cache entry is removed before an async operation settles.
3
+ */
4
+ export class CacheEntryRemovedError extends Error {
5
+ name = "CacheEntryRemovedError";
6
+ constructor(detail) {
7
+ super(`Cache entry removed before ${detail}`);
8
+ }
9
+ }
@@ -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
+ }