@fozy-labs/rx-toolkit 0.5.3-rc.1 → 0.5.3

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 (207) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +143 -137
  3. package/dist/common/devtools/combineDevtools.js +3 -3
  4. package/dist/common/devtools/index.d.ts +3 -3
  5. package/dist/common/devtools/index.js +3 -3
  6. package/dist/common/devtools/reduxDevtools.d.ts +1 -1
  7. package/dist/common/devtools/reduxDevtools.js +17 -17
  8. package/dist/common/devtools/types.d.ts +0 -6
  9. package/dist/common/options/DefaultOptions.d.ts +1 -1
  10. package/dist/common/options/SharedOptions.d.ts +3 -2
  11. package/dist/common/options/SharedOptions.js +6 -0
  12. package/dist/common/options/index.d.ts +1 -1
  13. package/dist/common/options/index.js +1 -1
  14. package/dist/common/react/index.d.ts +2 -2
  15. package/dist/common/react/index.js +2 -2
  16. package/dist/common/react/useConstant.js +1 -1
  17. package/dist/common/utils/deepEqual.js +1 -1
  18. package/dist/common/utils/index.d.ts +3 -3
  19. package/dist/common/utils/index.js +3 -3
  20. package/dist/common/utils/shallowEqual.js +1 -1
  21. package/dist/index.d.ts +8 -7
  22. package/dist/index.js +8 -7
  23. package/dist/query/SKIP_TOKEN.js +1 -1
  24. package/dist/query/api/createCommand.d.ts +21 -0
  25. package/dist/query/api/createCommand.js +20 -0
  26. package/dist/query/api/createOperation.d.ts +5 -3
  27. package/dist/query/api/createOperation.js +6 -2
  28. package/dist/query/api/createResource.d.ts +2 -2
  29. package/dist/query/api/createResourceDuplicator.d.ts +2 -2
  30. package/dist/query/core/Command/Command.d.ts +35 -0
  31. package/dist/query/core/{Opertation/Operation.js → Command/Command.js} +13 -14
  32. package/dist/query/core/Command/CommandAgent.d.ts +19 -0
  33. package/dist/query/core/{Opertation/OperationAgent.js → Command/CommandAgent.js} +13 -13
  34. package/dist/query/core/Command/index.d.ts +2 -0
  35. package/dist/query/core/Command/index.js +2 -0
  36. package/dist/query/core/Operation/Operation.d.ts +8 -0
  37. package/dist/query/core/Operation/Operation.js +4 -0
  38. package/dist/query/core/Operation/OperationAgent.d.ts +4 -0
  39. package/dist/query/core/Operation/OperationAgent.js +4 -0
  40. package/dist/query/core/QueriesCache.d.ts +2 -2
  41. package/dist/query/core/QueriesCache.js +1 -1
  42. package/dist/query/core/QueriesLifetimeHooks.d.ts +1 -1
  43. package/dist/query/core/QueriesLifetimeHooks.js +7 -7
  44. package/dist/query/core/Resource/Resource.d.ts +16 -16
  45. package/dist/query/core/Resource/Resource.js +7 -7
  46. package/dist/query/core/Resource/ResourceAgent.d.ts +2 -2
  47. package/dist/query/core/Resource/ResourceAgent.js +3 -3
  48. package/dist/query/core/Resource/ResourceDuplicator.d.ts +17 -17
  49. package/dist/query/core/Resource/ResourceDuplicator.js +18 -20
  50. package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +6 -6
  51. package/dist/query/core/Resource/ResourceDuplicatorAgent.js +3 -3
  52. package/dist/query/core/Resource/ResourceRef.d.ts +2 -2
  53. package/dist/query/core/Resource/ResourceRef.js +12 -12
  54. package/dist/query/index.d.ts +11 -8
  55. package/dist/query/index.js +14 -8
  56. package/dist/query/lib/IndirectMap.js +4 -4
  57. package/dist/query/lib/ReactiveCache.d.ts +1 -1
  58. package/dist/query/react/useCommandAgent.d.ts +24 -0
  59. package/dist/query/react/useCommandAgent.js +39 -0
  60. package/dist/query/react/useOperationAgent.d.ts +6 -8
  61. package/dist/query/react/useOperationAgent.js +6 -23
  62. package/dist/query/react/useResourceAgent.d.ts +4 -4
  63. package/dist/query/react/useResourceAgent.js +1 -1
  64. package/dist/query/react/useResourceRef.d.ts +3 -3
  65. package/dist/query/react/useResourceRef.js +7 -2
  66. package/dist/query/types/Command.types.d.ts +154 -0
  67. package/dist/query/types/Command.types.js +1 -0
  68. package/dist/query/types/Operation.types.d.ts +13 -154
  69. package/dist/query/types/Resource.types.d.ts +7 -5
  70. package/dist/query/types/index.d.ts +4 -3
  71. package/dist/query/types/index.js +5 -3
  72. package/dist/query-v2/api/createApi.d.ts +10 -0
  73. package/dist/query-v2/api/createApi.js +83 -0
  74. package/dist/query-v2/core/common/CacheEntry.d.ts +29 -0
  75. package/dist/query-v2/core/common/CacheEntry.js +71 -0
  76. package/dist/query-v2/core/common/CacheMap.d.ts +38 -0
  77. package/dist/query-v2/core/common/CacheMap.js +127 -0
  78. package/dist/query-v2/core/common/LifecycleHooks.d.ts +22 -0
  79. package/dist/query-v2/core/common/LifecycleHooks.js +104 -0
  80. package/dist/query-v2/core/common/index.d.ts +3 -0
  81. package/dist/query-v2/core/common/index.js +3 -0
  82. package/dist/query-v2/core/index.d.ts +3 -0
  83. package/dist/query-v2/core/index.js +3 -0
  84. package/dist/query-v2/core/machines/Machine.d.ts +14 -0
  85. package/dist/query-v2/core/machines/Machine.js +33 -0
  86. package/dist/query-v2/core/machines/MachineError.d.ts +11 -0
  87. package/dist/query-v2/core/machines/MachineError.js +26 -0
  88. package/dist/query-v2/core/machines/MachineIdle.d.ts +8 -0
  89. package/dist/query-v2/core/machines/MachineIdle.js +19 -0
  90. package/dist/query-v2/core/machines/MachinePending.d.ts +12 -0
  91. package/dist/query-v2/core/machines/MachinePending.js +29 -0
  92. package/dist/query-v2/core/machines/MachineRefreshing.d.ts +14 -0
  93. package/dist/query-v2/core/machines/MachineRefreshing.js +46 -0
  94. package/dist/query-v2/core/machines/MachineSuccess.d.ts +16 -0
  95. package/dist/query-v2/core/machines/MachineSuccess.js +42 -0
  96. package/dist/query-v2/core/machines/MachineWithData.d.ts +18 -0
  97. package/dist/query-v2/core/machines/MachineWithData.js +40 -0
  98. package/dist/query-v2/core/machines/Patcher.d.ts +20 -0
  99. package/dist/query-v2/core/machines/Patcher.js +104 -0
  100. package/dist/query-v2/core/machines/index.d.ts +8 -0
  101. package/dist/query-v2/core/machines/index.js +8 -0
  102. package/dist/query-v2/core/resource/ResourceV2.d.ts +120 -0
  103. package/dist/query-v2/core/resource/ResourceV2.js +464 -0
  104. package/dist/query-v2/core/resource/ResourceV2Agent.d.ts +26 -0
  105. package/dist/query-v2/core/resource/ResourceV2Agent.js +132 -0
  106. package/dist/query-v2/core/resource/index.d.ts +2 -0
  107. package/dist/query-v2/core/resource/index.js +2 -0
  108. package/dist/query-v2/index.d.ts +11 -0
  109. package/dist/query-v2/index.js +17 -0
  110. package/dist/query-v2/lib/NO_VALUE.d.ts +2 -0
  111. package/dist/query-v2/lib/NO_VALUE.js +1 -0
  112. package/dist/query-v2/lib/SKIP_TOKEN.d.ts +2 -0
  113. package/dist/query-v2/lib/SKIP_TOKEN.js +1 -0
  114. package/dist/query-v2/lib/index.d.ts +4 -0
  115. package/dist/query-v2/lib/index.js +3 -0
  116. package/dist/query-v2/lib/stableStringify.d.ts +8 -0
  117. package/dist/query-v2/lib/stableStringify.js +23 -0
  118. package/dist/query-v2/plugins/ReactHooksPlugin.d.ts +25 -0
  119. package/dist/query-v2/plugins/ReactHooksPlugin.js +19 -0
  120. package/dist/query-v2/plugins/types.d.ts +1 -0
  121. package/dist/query-v2/plugins/types.js +1 -0
  122. package/dist/query-v2/react/__tests__/helpers.d.ts +12 -0
  123. package/dist/query-v2/react/__tests__/helpers.js +33 -0
  124. package/dist/query-v2/react/index.d.ts +2 -0
  125. package/dist/query-v2/react/index.js +2 -0
  126. package/dist/query-v2/react/useResourceV2Agent.d.ts +12 -0
  127. package/dist/query-v2/react/useResourceV2Agent.js +36 -0
  128. package/dist/query-v2/react/useResourceV2Ref.d.ts +12 -0
  129. package/dist/query-v2/react/useResourceV2Ref.js +57 -0
  130. package/dist/query-v2/snapshot/Snapshot.d.ts +13 -0
  131. package/dist/query-v2/snapshot/Snapshot.js +76 -0
  132. package/dist/query-v2/types/agent.types.d.ts +54 -0
  133. package/dist/query-v2/types/agent.types.js +1 -0
  134. package/dist/query-v2/types/api.types.d.ts +22 -0
  135. package/dist/query-v2/types/api.types.js +1 -0
  136. package/dist/query-v2/types/cache.types.d.ts +37 -0
  137. package/dist/query-v2/types/cache.types.js +1 -0
  138. package/dist/query-v2/types/index.d.ts +9 -0
  139. package/dist/query-v2/types/index.js +9 -0
  140. package/dist/query-v2/types/lifecycle.types.d.ts +25 -0
  141. package/dist/query-v2/types/lifecycle.types.js +1 -0
  142. package/dist/query-v2/types/machine.types.d.ts +67 -0
  143. package/dist/query-v2/types/machine.types.js +1 -0
  144. package/dist/query-v2/types/plugin.types.d.ts +38 -0
  145. package/dist/query-v2/types/plugin.types.js +1 -0
  146. package/dist/query-v2/types/resource.types.d.ts +35 -0
  147. package/dist/query-v2/types/resource.types.js +1 -0
  148. package/dist/query-v2/types/shared.types.d.ts +20 -0
  149. package/dist/query-v2/types/shared.types.js +1 -0
  150. package/dist/query-v2/types/snapshot.types.d.ts +21 -0
  151. package/dist/query-v2/types/snapshot.types.js +1 -0
  152. package/dist/signals/base/Batcher.js +9 -5
  153. package/dist/signals/base/ComputeCache.js +3 -3
  154. package/dist/signals/base/DependencyTracker.js +1 -1
  155. package/dist/signals/base/Devtools.d.ts +3 -2
  156. package/dist/signals/base/Devtools.js +54 -27
  157. package/dist/signals/base/Indexer.js +1 -1
  158. package/dist/signals/base/ReadonlySignal.js +1 -1
  159. package/dist/signals/base/SyncObservable.d.ts +1 -2
  160. package/dist/signals/base/SyncObservable.js +2 -5
  161. package/dist/signals/base/index.d.ts +6 -6
  162. package/dist/signals/base/index.js +6 -6
  163. package/dist/signals/index.d.ts +5 -4
  164. package/dist/signals/index.js +5 -4
  165. package/dist/signals/operators/index.d.ts +1 -1
  166. package/dist/signals/operators/index.js +1 -1
  167. package/dist/signals/operators/signalize.d.ts +1 -1
  168. package/dist/signals/react/index.d.ts +1 -1
  169. package/dist/signals/react/index.js +1 -1
  170. package/dist/signals/signals/Computed.d.ts +3 -4
  171. package/dist/signals/signals/Computed.js +18 -10
  172. package/dist/signals/signals/Effect.js +2 -1
  173. package/dist/signals/signals/LocalState.d.ts +44 -0
  174. package/dist/signals/signals/{LocalSignal.js → LocalState.js} +62 -28
  175. package/dist/signals/signals/Signal.d.ts +8 -7
  176. package/dist/signals/signals/Signal.js +4 -1
  177. package/dist/signals/signals/State.d.ts +4 -5
  178. package/dist/signals/signals/State.js +23 -9
  179. package/dist/signals/signals/index.d.ts +5 -5
  180. package/dist/signals/signals/index.js +5 -6
  181. package/dist/signals/types/SignalOptions.d.ts +16 -0
  182. package/dist/signals/types/SignalOptions.js +1 -0
  183. package/dist/signals/types/index.d.ts +3 -1
  184. package/dist/signals/types/index.js +3 -1
  185. package/dist/signals/types/normalizeSignalOptions.d.ts +2 -0
  186. package/dist/signals/types/normalizeSignalOptions.js +10 -0
  187. package/dist/signals/types/signals.types.d.ts +6 -2
  188. package/docs/CHANGELOG.md +111 -32
  189. package/docs/CONTRIBUTING.md +230 -0
  190. package/docs/contributing/ai-assisted-development.md +47 -0
  191. package/docs/contributing/query-v2/README.md +379 -0
  192. package/docs/{release → contributing/release}/README.md +59 -59
  193. package/docs/devtools/README.md +228 -228
  194. package/docs/migrations/0.5.0.md +58 -58
  195. package/docs/migrations/query-v2.md +171 -0
  196. package/docs/options/README.md +92 -90
  197. package/docs/query/README.md +575 -571
  198. package/docs/query-v2/README.md +280 -0
  199. package/docs/query-v2/api-reference.md +235 -0
  200. package/docs/query-v2/optimistic-updates.md +148 -0
  201. package/docs/query-v2/ssr.md +130 -0
  202. package/docs/signals/README.md +300 -295
  203. package/docs/usage/react/README.md +309 -307
  204. package/package.json +86 -63
  205. package/dist/query/core/Opertation/Operation.d.ts +0 -35
  206. package/dist/query/core/Opertation/OperationAgent.d.ts +0 -19
  207. package/dist/signals/signals/LocalSignal.d.ts +0 -32
@@ -0,0 +1,104 @@
1
+ import { PromiseResolver } from "../../../common/utils/PromiseResolver";
2
+ export class LifecycleHooks {
3
+ _onCacheEntryAdded;
4
+ _onQueryStarted;
5
+ _cacheEntryState = new Map();
6
+ _queryState = null;
7
+ _serializeArgs;
8
+ constructor(options) {
9
+ this._onCacheEntryAdded = options.onCacheEntryAdded;
10
+ this._onQueryStarted = options.onQueryStarted;
11
+ this._serializeArgs = options.serializeArgs;
12
+ }
13
+ fireCacheEntryAdded(args, getCacheEntry) {
14
+ if (!this._onCacheEntryAdded)
15
+ return;
16
+ const key = this._serializeArgs(args);
17
+ const $cacheDataLoaded = new PromiseResolver();
18
+ const $cacheEntryRemoved = new PromiseResolver();
19
+ // Prevent unhandled rejection if no consumer attaches .catch()
20
+ $cacheDataLoaded.promise.catch(() => { });
21
+ $cacheEntryRemoved.promise.catch(() => { });
22
+ const state = {
23
+ $cacheDataLoaded,
24
+ $cacheEntryRemoved,
25
+ dataLoaded: false,
26
+ };
27
+ this._cacheEntryState.set(key, state);
28
+ const tools = {
29
+ $cacheDataLoaded: $cacheDataLoaded.promise,
30
+ $cacheEntryRemoved: $cacheEntryRemoved.promise,
31
+ getCacheEntry: getCacheEntry,
32
+ };
33
+ this._onCacheEntryAdded(args, tools);
34
+ }
35
+ fireCacheEntryRemoved(args) {
36
+ const key = this._serializeArgs(args);
37
+ const state = this._cacheEntryState.get(key);
38
+ if (!state)
39
+ return;
40
+ if (!state.dataLoaded) {
41
+ state.$cacheDataLoaded.reject(new Error("Cache entry removed before data loaded"));
42
+ }
43
+ state.$cacheEntryRemoved.resolve();
44
+ this._cacheEntryState.delete(key);
45
+ }
46
+ resolveCacheDataLoaded(args, data) {
47
+ const key = this._serializeArgs(args);
48
+ const state = this._cacheEntryState.get(key);
49
+ if (!state || state.dataLoaded)
50
+ return;
51
+ state.dataLoaded = true;
52
+ state.$cacheDataLoaded.resolve(data);
53
+ }
54
+ fireQueryStarted(args, getCacheEntry) {
55
+ if (!this._onQueryStarted) {
56
+ // Still create state for resolving/rejecting even without callback
57
+ const resolver = new PromiseResolver();
58
+ resolver.promise.catch(() => { });
59
+ this._queryState = {
60
+ $queryFulfilled: resolver,
61
+ };
62
+ return;
63
+ }
64
+ const $queryFulfilled = new PromiseResolver();
65
+ // Prevent unhandled rejection if consumer doesn't attach .catch()
66
+ $queryFulfilled.promise.catch(() => { });
67
+ this._queryState = { $queryFulfilled };
68
+ const tools = {
69
+ $queryFulfilled: $queryFulfilled.promise,
70
+ getCacheEntry: () => getCacheEntry(),
71
+ };
72
+ this._onQueryStarted(args, tools);
73
+ }
74
+ resolveQueryFulfilled(data) {
75
+ if (!this._queryState)
76
+ return;
77
+ this._queryState.$queryFulfilled.resolve({ data, isError: false });
78
+ this._queryState = null;
79
+ }
80
+ rejectQueryFulfilled(error) {
81
+ if (!this._queryState)
82
+ return;
83
+ this._queryState.$queryFulfilled.reject(error);
84
+ this._queryState = null;
85
+ }
86
+ clearAll() {
87
+ for (const [, state] of this._cacheEntryState) {
88
+ if (!state.dataLoaded) {
89
+ state.$cacheDataLoaded.reject(new Error("Cache entry removed before data loaded"));
90
+ }
91
+ state.$cacheEntryRemoved.resolve();
92
+ }
93
+ this._cacheEntryState.clear();
94
+ if (this._queryState) {
95
+ try {
96
+ this._queryState.$queryFulfilled.reject(new Error("Resource reset"));
97
+ }
98
+ catch {
99
+ // Already settled
100
+ }
101
+ this._queryState = null;
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,3 @@
1
+ export { CacheEntry, type CacheEntryOptions } from "./CacheEntry";
2
+ export { CacheMap, type TCacheMapInstance } from "./CacheMap";
3
+ export { LifecycleHooks } from "./LifecycleHooks";
@@ -0,0 +1,3 @@
1
+ export { CacheEntry } from "./CacheEntry";
2
+ export { CacheMap } from "./CacheMap";
3
+ export { LifecycleHooks } from "./LifecycleHooks";
@@ -0,0 +1,3 @@
1
+ export * from "./common";
2
+ export * from "./machines";
3
+ export * from "./resource";
@@ -0,0 +1,3 @@
1
+ export * from "./common";
2
+ export * from "./machines";
3
+ export * from "./resource";
@@ -0,0 +1,14 @@
1
+ import type { TMachineStatus } from "../../../query-v2/types/machine.types";
2
+ import { MachineError } from "./MachineError";
3
+ import { MachineIdle } from "./MachineIdle";
4
+ import { MachinePending } from "./MachinePending";
5
+ import { MachineRefreshing } from "./MachineRefreshing";
6
+ import { MachineSuccess } from "./MachineSuccess";
7
+ export type TMachineInstance<TData = unknown, TError = Error> = MachineIdle | MachinePending<TData> | MachineSuccess<TData> | MachineError<TError> | MachineRefreshing<TData>;
8
+ export declare const Machine: {
9
+ readonly idle: () => MachineIdle;
10
+ readonly fromSnapshot: <TData>(state: {
11
+ status: TMachineStatus;
12
+ } & Record<string, unknown>) => TMachineInstance<TData>;
13
+ };
14
+ export { MachineIdle, MachinePending, MachineSuccess, MachineError, MachineRefreshing };
@@ -0,0 +1,33 @@
1
+ import { NO_VALUE as NO_VALUE_VALUE } from "../../../query-v2/lib/NO_VALUE";
2
+ import { MachineError } from "./MachineError";
3
+ import { MachineIdle } from "./MachineIdle";
4
+ import { MachinePending } from "./MachinePending";
5
+ import { MachineRefreshing } from "./MachineRefreshing";
6
+ import { MachineSuccess } from "./MachineSuccess";
7
+ export const Machine = {
8
+ idle() {
9
+ return MachineIdle.create();
10
+ },
11
+ fromSnapshot(state) {
12
+ switch (state.status) {
13
+ case "idle":
14
+ return MachineIdle.create();
15
+ case "pending":
16
+ return MachinePending.create(state.args ?? null);
17
+ case "success":
18
+ return MachineSuccess.deploy({
19
+ status: "success",
20
+ args: state.args ?? null,
21
+ data: state.data,
22
+ updatedAt: state.updatedAt ?? Date.now(),
23
+ });
24
+ case "error":
25
+ return MachineError.create((state.error ?? new Error("Unknown error")), state.args ?? null);
26
+ case "refreshing":
27
+ return MachineRefreshing.create(state.data, state.args ?? null, state.updatedAt ?? Date.now(), NO_VALUE_VALUE, null);
28
+ default:
29
+ throw new Error(`Unknown machine status: ${state.status}`);
30
+ }
31
+ },
32
+ };
33
+ export { MachineIdle, MachinePending, MachineSuccess, MachineError, MachineRefreshing };
@@ -0,0 +1,11 @@
1
+ import type { TResourceV2ErrorState } from "../../../query-v2/types/machine.types";
2
+ import { MachineIdle } from "./MachineIdle";
3
+ import { MachinePending } from "./MachinePending";
4
+ export declare class MachineError<TError = Error> {
5
+ readonly state: TResourceV2ErrorState<TError>;
6
+ private constructor();
7
+ retry(): MachinePending;
8
+ start(args: unknown): MachinePending;
9
+ reset(): MachineIdle;
10
+ static create<TError = Error>(error: TError, args: unknown): MachineError<TError>;
11
+ }
@@ -0,0 +1,26 @@
1
+ import { MachineIdle } from "./MachineIdle";
2
+ import { MachinePending } from "./MachinePending";
3
+ export class MachineError {
4
+ state;
5
+ constructor(error, args) {
6
+ this.state = {
7
+ status: "error",
8
+ args,
9
+ data: null,
10
+ error,
11
+ updatedAt: null,
12
+ };
13
+ }
14
+ retry() {
15
+ return MachinePending.create(this.state.args);
16
+ }
17
+ start(args) {
18
+ return MachinePending.create(args);
19
+ }
20
+ reset() {
21
+ return MachineIdle.create();
22
+ }
23
+ static create(error, args) {
24
+ return new MachineError(error, args);
25
+ }
26
+ }
@@ -0,0 +1,8 @@
1
+ import type { TResourceV2IdleState } from "../../../query-v2/types/machine.types";
2
+ import { MachinePending } from "./MachinePending";
3
+ export declare class MachineIdle {
4
+ readonly state: TResourceV2IdleState;
5
+ start(args: unknown): MachinePending;
6
+ reset(): MachineIdle;
7
+ static create(): MachineIdle;
8
+ }
@@ -0,0 +1,19 @@
1
+ import { MachinePending } from "./MachinePending";
2
+ export class MachineIdle {
3
+ state = {
4
+ status: "idle",
5
+ args: null,
6
+ data: null,
7
+ error: null,
8
+ updatedAt: null,
9
+ };
10
+ start(args) {
11
+ return MachinePending.create(args);
12
+ }
13
+ reset() {
14
+ return this;
15
+ }
16
+ static create() {
17
+ return new MachineIdle();
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ import type { TResourceV2PendingState } from "../../../query-v2/types/machine.types";
2
+ import { MachineError } from "./MachineError";
3
+ import { MachineIdle } from "./MachineIdle";
4
+ import { MachineSuccess } from "./MachineSuccess";
5
+ export declare class MachinePending<TData = unknown> {
6
+ readonly state: TResourceV2PendingState<TData>;
7
+ private constructor();
8
+ successHappened(data: TData): MachineSuccess<TData>;
9
+ errorHappened<TError = Error>(error: TError): MachineError<TError>;
10
+ reset(): MachineIdle;
11
+ static create<TData = unknown>(args: unknown): MachinePending<TData>;
12
+ }
@@ -0,0 +1,29 @@
1
+ import { NO_VALUE as NO_VALUE_VALUE } from "../../../query-v2/lib/NO_VALUE";
2
+ import { MachineError } from "./MachineError";
3
+ import { MachineIdle } from "./MachineIdle";
4
+ import { MachineSuccess } from "./MachineSuccess";
5
+ export class MachinePending {
6
+ state;
7
+ constructor(args) {
8
+ this.state = {
9
+ status: "pending",
10
+ args,
11
+ data: null,
12
+ error: null,
13
+ updatedAt: null,
14
+ originalData: NO_VALUE_VALUE,
15
+ };
16
+ }
17
+ successHappened(data) {
18
+ return MachineSuccess.create(data, this.state.args);
19
+ }
20
+ errorHappened(error) {
21
+ return MachineError.create(error, this.state.args);
22
+ }
23
+ reset() {
24
+ return MachineIdle.create();
25
+ }
26
+ static create(args) {
27
+ return new MachinePending(args);
28
+ }
29
+ }
@@ -0,0 +1,14 @@
1
+ import type { NO_VALUE } from "../../../query-v2/lib/NO_VALUE";
2
+ import type { TResourceV2Patch, TResourceV2RefreshingState } from "../../../query-v2/types/machine.types";
3
+ import { MachineIdle } from "./MachineIdle";
4
+ import { MachineSuccess } from "./MachineSuccess";
5
+ import { MachineWithData, type MachineWithDataState } from "./MachineWithData";
6
+ export declare class MachineRefreshing<TData = unknown> extends MachineWithData<TData> {
7
+ readonly state: TResourceV2RefreshingState<TData>;
8
+ private constructor();
9
+ protected cloneWith(updates: Partial<MachineWithDataState<TData>>): this;
10
+ successHappened(data: TData): MachineSuccess<TData>;
11
+ errorHappened(_error: unknown): MachineSuccess<TData>;
12
+ reset(): MachineIdle;
13
+ static create<TData>(data: TData, args: unknown, updatedAt: number, originalData?: TData | NO_VALUE, patches?: TResourceV2Patch[] | null): MachineRefreshing<TData>;
14
+ }
@@ -0,0 +1,46 @@
1
+ import { NO_VALUE as NO_VALUE_VALUE } from "../../../query-v2/lib/NO_VALUE";
2
+ import { MachineIdle } from "./MachineIdle";
3
+ import { MachineSuccess } from "./MachineSuccess";
4
+ import { MachineWithData } from "./MachineWithData";
5
+ export class MachineRefreshing extends MachineWithData {
6
+ state;
7
+ constructor(data, args, updatedAt, originalData = NO_VALUE_VALUE, patches = null) {
8
+ super();
9
+ this.state = {
10
+ status: "refreshing",
11
+ args,
12
+ data,
13
+ error: null,
14
+ updatedAt,
15
+ originalData,
16
+ patches,
17
+ };
18
+ }
19
+ cloneWith(updates) {
20
+ return new MachineRefreshing(updates.data ?? this.state.data, this.state.args, this.state.updatedAt, updates.originalData !== undefined ? updates.originalData : this.state.originalData, updates.patches !== undefined ? updates.patches : this.state.patches);
21
+ }
22
+ successHappened(data) {
23
+ // Fresh data supersedes optimistic patches — abort all pending (per ADR-4, E11)
24
+ const cleaned = this.abortAllPendingPatches();
25
+ // Ignore cleaned data — fresh data from server replaces everything
26
+ void cleaned;
27
+ return MachineSuccess.create(data, this.state.args);
28
+ }
29
+ errorHappened(_error) {
30
+ // ADR-2: Preserve stale data on refresh error
31
+ // Return MachineSuccess with original stale data, same updatedAt, preserve patches
32
+ return MachineSuccess.deploy({
33
+ status: "success",
34
+ args: this.state.args,
35
+ data: this.state.data,
36
+ updatedAt: this.state.updatedAt,
37
+ });
38
+ }
39
+ reset() {
40
+ this.abortAllPendingPatches();
41
+ return MachineIdle.create();
42
+ }
43
+ static create(data, args, updatedAt, originalData = NO_VALUE_VALUE, patches = null) {
44
+ return new MachineRefreshing(data, args, updatedAt, originalData, patches);
45
+ }
46
+ }
@@ -0,0 +1,16 @@
1
+ import type { TResourceV2SuccessState } from "../../../query-v2/types/machine.types";
2
+ import type { TResourceV2SnapshotSlice } from "../../../query-v2/types/snapshot.types";
3
+ import { MachineIdle } from "./MachineIdle";
4
+ import { MachinePending } from "./MachinePending";
5
+ import { MachineRefreshing } from "./MachineRefreshing";
6
+ import { MachineWithData, type MachineWithDataState } from "./MachineWithData";
7
+ export declare class MachineSuccess<TData = unknown> extends MachineWithData<TData> {
8
+ readonly state: TResourceV2SuccessState<TData>;
9
+ private constructor();
10
+ protected cloneWith(updates: Partial<MachineWithDataState<TData>>): this;
11
+ invalidate(): MachineRefreshing<TData>;
12
+ start(args: unknown): MachinePending<TData>;
13
+ reset(): MachineIdle;
14
+ static create<TData>(data: TData, args: unknown): MachineSuccess<TData>;
15
+ static deploy<TData = unknown>(snapshotSlice: TResourceV2SnapshotSlice<TData>): MachineSuccess<TData>;
16
+ }
@@ -0,0 +1,42 @@
1
+ import { NO_VALUE as NO_VALUE_VALUE } from "../../../query-v2/lib/NO_VALUE";
2
+ import { MachineIdle } from "./MachineIdle";
3
+ import { MachinePending } from "./MachinePending";
4
+ import { MachineRefreshing } from "./MachineRefreshing";
5
+ import { MachineWithData } from "./MachineWithData";
6
+ export class MachineSuccess extends MachineWithData {
7
+ state;
8
+ constructor(data, args, updatedAt, originalData = NO_VALUE_VALUE, patches = null) {
9
+ super();
10
+ this.state = {
11
+ status: "success",
12
+ args,
13
+ data,
14
+ error: null,
15
+ updatedAt,
16
+ originalData,
17
+ patches,
18
+ };
19
+ }
20
+ cloneWith(updates) {
21
+ return new MachineSuccess(updates.data ?? this.state.data, this.state.args, this.state.updatedAt, updates.originalData !== undefined ? updates.originalData : this.state.originalData, updates.patches !== undefined ? updates.patches : this.state.patches);
22
+ }
23
+ invalidate() {
24
+ return MachineRefreshing.create(this.state.data, this.state.args, this.state.updatedAt, this.state.originalData, this.state.patches);
25
+ }
26
+ start(args) {
27
+ // Abort pending patches before transitioning (per ADR-4)
28
+ this.abortAllPendingPatches();
29
+ return MachinePending.create(args);
30
+ }
31
+ reset() {
32
+ // Abort pending patches before transitioning (per ADR-4)
33
+ this.abortAllPendingPatches();
34
+ return MachineIdle.create();
35
+ }
36
+ static create(data, args) {
37
+ return new MachineSuccess(data, args, Date.now());
38
+ }
39
+ static deploy(snapshotSlice) {
40
+ return new MachineSuccess(snapshotSlice.data, snapshotSlice.args, snapshotSlice.updatedAt);
41
+ }
42
+ }
@@ -0,0 +1,18 @@
1
+ import type { NO_VALUE } from "../../../query-v2/lib/NO_VALUE";
2
+ import type { TPatchFn, TResourceV2Patch } from "../../../query-v2/types/machine.types";
3
+ export interface MachineWithDataState<TData> {
4
+ data: TData;
5
+ originalData: TData | NO_VALUE;
6
+ patches: TResourceV2Patch[] | null;
7
+ }
8
+ export declare abstract class MachineWithData<TData = unknown> {
9
+ abstract readonly state: MachineWithDataState<TData>;
10
+ protected abstract cloneWith(updates: Partial<MachineWithDataState<TData>>): this;
11
+ addPatch(patch: TResourceV2Patch): this;
12
+ finishPatch(type: "commit" | "abort", patch: TResourceV2Patch): this;
13
+ createPatch<TSelf extends MachineWithData<TData>>(this: TSelf, patchFn: TPatchFn<TData>): {
14
+ machine: TSelf;
15
+ patch: TResourceV2Patch;
16
+ };
17
+ abortAllPendingPatches(): this;
18
+ }
@@ -0,0 +1,40 @@
1
+ import { NO_VALUE as NO_VALUE_VALUE } from "../../../query-v2/lib/NO_VALUE";
2
+ import { Patcher } from "./Patcher";
3
+ export class MachineWithData {
4
+ addPatch(patch) {
5
+ const { state } = this;
6
+ const originalData = (state.originalData === NO_VALUE_VALUE ? state.data : state.originalData);
7
+ const currentPatches = state.patches ? [...state.patches, patch] : [patch];
8
+ const resolved = Patcher.resolvePatches(originalData, currentPatches);
9
+ return this.cloneWith({
10
+ data: resolved.data,
11
+ originalData,
12
+ patches: resolved.patches.length > 0 ? resolved.patches : null,
13
+ });
14
+ }
15
+ finishPatch(type, patch) {
16
+ const { state } = this;
17
+ const result = Patcher.finishPatch(state.originalData, state.patches, type, patch);
18
+ return this.cloneWith({
19
+ data: result.data ?? state.data,
20
+ originalData: result.originalData,
21
+ patches: result.patches,
22
+ });
23
+ }
24
+ createPatch(patchFn) {
25
+ const { state } = this;
26
+ const dataForPatch = state.data;
27
+ const patch = Patcher.createPatch(patchFn, dataForPatch);
28
+ const machine = this.addPatch(patch);
29
+ return { machine, patch };
30
+ }
31
+ abortAllPendingPatches() {
32
+ const { state } = this;
33
+ const result = Patcher.abortAllPending(state.originalData, state.patches);
34
+ return this.cloneWith({
35
+ data: result.data ?? state.data,
36
+ originalData: result.originalData,
37
+ patches: result.patches,
38
+ });
39
+ }
40
+ }
@@ -0,0 +1,20 @@
1
+ import { NO_VALUE } from "../../../query-v2/lib/NO_VALUE";
2
+ import type { TPatchFn, TResourceV2Patch } from "../../../query-v2/types/machine.types";
3
+ export declare class Patcher {
4
+ static createPatch<TData>(patchFn: TPatchFn<TData>, data: TData): TResourceV2Patch;
5
+ static resolvePatches<TData>(originalData: TData, patches: TResourceV2Patch[]): {
6
+ data: TData;
7
+ patches: TResourceV2Patch[];
8
+ baseData: TData;
9
+ };
10
+ static finishPatch<TData>(originalData: TData | typeof NO_VALUE, patches: TResourceV2Patch[] | null, type: "commit" | "abort", patch: TResourceV2Patch): {
11
+ originalData: TData | typeof NO_VALUE;
12
+ patches: TResourceV2Patch[] | null;
13
+ data: TData | null;
14
+ };
15
+ static abortAllPending<TData>(originalData: TData | typeof NO_VALUE, patches: TResourceV2Patch[] | null): {
16
+ originalData: TData | typeof NO_VALUE;
17
+ patches: TResourceV2Patch[] | null;
18
+ data: TData | null;
19
+ };
20
+ }
@@ -0,0 +1,104 @@
1
+ import { applyPatches, enablePatches, produceWithPatches } from "immer";
2
+ import { NO_VALUE } from "../../../query-v2/lib/NO_VALUE";
3
+ enablePatches();
4
+ function applyImmerPatches(data, patches) {
5
+ return applyPatches(data, patches);
6
+ }
7
+ export class Patcher {
8
+ static createPatch(patchFn, data) {
9
+ const [, patches, inversePatches] = produceWithPatches(data, patchFn);
10
+ return {
11
+ patches,
12
+ inversePatches,
13
+ status: "pending",
14
+ };
15
+ }
16
+ static resolvePatches(originalData, patches) {
17
+ let currentData = originalData;
18
+ let baseData = originalData;
19
+ const remainingPatches = [];
20
+ let foundPending = false;
21
+ const lastPendingIndex = patches.findLastIndex((p) => p.status === "pending");
22
+ patches.forEach((patch, index) => {
23
+ if (patch.status === "pending") {
24
+ foundPending = true;
25
+ currentData = applyImmerPatches(currentData, patch.patches);
26
+ remainingPatches.push(patch);
27
+ }
28
+ else if (foundPending) {
29
+ if (patch.status === "committed") {
30
+ currentData = applyImmerPatches(currentData, patch.patches);
31
+ remainingPatches.push(patch);
32
+ }
33
+ else if (patch.status === "aborted") {
34
+ const hasPendingAfter = index < lastPendingIndex;
35
+ if (hasPendingAfter) {
36
+ currentData = applyImmerPatches(currentData, patch.inversePatches);
37
+ remainingPatches.push(patch);
38
+ }
39
+ // No pending after → remove from queue
40
+ }
41
+ }
42
+ else {
43
+ // Before first pending
44
+ if (patch.status === "committed") {
45
+ currentData = applyImmerPatches(currentData, patch.patches);
46
+ baseData = currentData;
47
+ // committed before pending → remove from queue
48
+ }
49
+ else if (patch.status === "aborted") {
50
+ // aborted before pending → remove from queue
51
+ }
52
+ }
53
+ });
54
+ return { data: currentData, patches: remainingPatches, baseData };
55
+ }
56
+ static finishPatch(originalData, patches, type, patch) {
57
+ if (!patches) {
58
+ return { originalData, patches: null, data: null };
59
+ }
60
+ // Mark the target patch
61
+ patch.status = type === "commit" ? "committed" : "aborted";
62
+ // If no originalData, nothing to resolve
63
+ if (originalData === NO_VALUE) {
64
+ return { originalData, patches, data: null };
65
+ }
66
+ // Resolve patches
67
+ const resolved = Patcher.resolvePatches(originalData, patches);
68
+ const hasPending = resolved.patches.some((p) => p.status === "pending");
69
+ if (!hasPending) {
70
+ // No pending patches remain → clear originalData and patches
71
+ return {
72
+ originalData: NO_VALUE,
73
+ patches: resolved.patches.length > 0 ? resolved.patches : null,
74
+ data: resolved.data,
75
+ };
76
+ }
77
+ return {
78
+ originalData: resolved.baseData,
79
+ patches: resolved.patches.length > 0 ? resolved.patches : null,
80
+ data: resolved.data,
81
+ };
82
+ }
83
+ static abortAllPending(originalData, patches) {
84
+ if (!patches) {
85
+ return { originalData, patches: null, data: null };
86
+ }
87
+ // Mark all pending as aborted
88
+ for (const patch of patches) {
89
+ if (patch.status === "pending") {
90
+ patch.status = "aborted";
91
+ }
92
+ }
93
+ if (originalData === NO_VALUE) {
94
+ return { originalData: NO_VALUE, patches: null, data: null };
95
+ }
96
+ // Resolve: all should be either committed or aborted now
97
+ const resolved = Patcher.resolvePatches(originalData, patches);
98
+ return {
99
+ originalData: NO_VALUE,
100
+ patches: null,
101
+ data: resolved.data,
102
+ };
103
+ }
104
+ }
@@ -0,0 +1,8 @@
1
+ export { Machine, type TMachineInstance } from "./Machine";
2
+ export { MachineIdle } from "./MachineIdle";
3
+ export { MachinePending } from "./MachinePending";
4
+ export { MachineSuccess } from "./MachineSuccess";
5
+ export { MachineError } from "./MachineError";
6
+ export { MachineRefreshing } from "./MachineRefreshing";
7
+ export { MachineWithData } from "./MachineWithData";
8
+ export { Patcher } from "./Patcher";
@@ -0,0 +1,8 @@
1
+ export { Machine } from "./Machine";
2
+ export { MachineIdle } from "./MachineIdle";
3
+ export { MachinePending } from "./MachinePending";
4
+ export { MachineSuccess } from "./MachineSuccess";
5
+ export { MachineError } from "./MachineError";
6
+ export { MachineRefreshing } from "./MachineRefreshing";
7
+ export { MachineWithData } from "./MachineWithData";
8
+ export { Patcher } from "./Patcher";