@fozy-labs/rx-toolkit 0.5.3-rc.2 → 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.
- package/LICENSE +21 -21
- package/README.md +143 -137
- package/dist/common/devtools/combineDevtools.js +3 -3
- package/dist/common/devtools/index.d.ts +3 -3
- package/dist/common/devtools/index.js +3 -3
- package/dist/common/devtools/reduxDevtools.d.ts +1 -1
- package/dist/common/devtools/reduxDevtools.js +17 -17
- package/dist/common/devtools/types.d.ts +0 -6
- package/dist/common/options/SharedOptions.d.ts +1 -0
- package/dist/common/options/SharedOptions.js +6 -0
- package/dist/common/options/index.d.ts +1 -1
- package/dist/common/options/index.js +1 -1
- package/dist/common/react/index.d.ts +2 -2
- package/dist/common/react/index.js +2 -2
- package/dist/common/react/useConstant.js +1 -1
- package/dist/common/utils/deepEqual.js +1 -1
- package/dist/common/utils/index.d.ts +3 -3
- package/dist/common/utils/index.js +3 -3
- package/dist/common/utils/shallowEqual.js +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +8 -7
- package/dist/query/SKIP_TOKEN.js +1 -1
- package/dist/query/api/createCommand.d.ts +1 -1
- package/dist/query/api/createOperation.d.ts +1 -1
- package/dist/query/api/createOperation.js +1 -1
- package/dist/query/api/createResource.d.ts +1 -1
- package/dist/query/api/createResourceDuplicator.d.ts +1 -1
- package/dist/query/core/Command/Command.d.ts +7 -7
- package/dist/query/core/Command/Command.js +2 -2
- package/dist/query/core/Command/CommandAgent.d.ts +1 -1
- package/dist/query/core/Command/index.d.ts +2 -2
- package/dist/query/core/Command/index.js +2 -2
- package/dist/query/core/{Opertation → Operation}/Operation.d.ts +2 -2
- package/dist/query/core/{Opertation → Operation}/Operation.js +1 -1
- package/dist/query/core/{Opertation → Operation}/OperationAgent.d.ts +1 -1
- package/dist/query/core/{Opertation → Operation}/OperationAgent.js +1 -1
- package/dist/query/core/QueriesCache.d.ts +1 -1
- package/dist/query/core/QueriesCache.js +1 -1
- package/dist/query/core/QueriesLifetimeHooks.js +7 -7
- package/dist/query/core/Resource/Resource.d.ts +15 -15
- package/dist/query/core/Resource/Resource.js +7 -7
- package/dist/query/core/Resource/ResourceAgent.d.ts +1 -1
- package/dist/query/core/Resource/ResourceAgent.js +2 -2
- package/dist/query/core/Resource/ResourceDuplicator.d.ts +16 -16
- package/dist/query/core/Resource/ResourceDuplicator.js +18 -20
- package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +5 -5
- package/dist/query/core/Resource/ResourceDuplicatorAgent.js +2 -2
- package/dist/query/core/Resource/ResourceRef.d.ts +2 -2
- package/dist/query/core/Resource/ResourceRef.js +12 -12
- package/dist/query/index.d.ts +11 -10
- package/dist/query/index.js +11 -10
- package/dist/query/lib/IndirectMap.js +4 -4
- package/dist/query/react/useCommandAgent.d.ts +2 -2
- package/dist/query/react/useOperationAgent.d.ts +1 -1
- package/dist/query/react/useOperationAgent.js +1 -1
- package/dist/query/react/useResourceAgent.d.ts +3 -3
- package/dist/query/react/useResourceAgent.js +1 -1
- package/dist/query/react/useResourceRef.d.ts +3 -3
- package/dist/query/react/useResourceRef.js +7 -2
- package/dist/query/types/Command.types.d.ts +1 -1
- package/dist/query/types/Operation.types.d.ts +1 -1
- package/dist/query/types/Resource.types.d.ts +7 -5
- package/dist/query/types/index.d.ts +4 -4
- package/dist/query/types/index.js +4 -4
- package/dist/query-v2/api/createApi.d.ts +10 -0
- package/dist/query-v2/api/createApi.js +83 -0
- package/dist/query-v2/core/common/CacheEntry.d.ts +29 -0
- package/dist/query-v2/core/common/CacheEntry.js +71 -0
- package/dist/query-v2/core/common/CacheMap.d.ts +38 -0
- package/dist/query-v2/core/common/CacheMap.js +127 -0
- package/dist/query-v2/core/common/LifecycleHooks.d.ts +22 -0
- package/dist/query-v2/core/common/LifecycleHooks.js +104 -0
- package/dist/query-v2/core/common/index.d.ts +3 -0
- package/dist/query-v2/core/common/index.js +3 -0
- package/dist/query-v2/core/index.d.ts +3 -0
- package/dist/query-v2/core/index.js +3 -0
- package/dist/query-v2/core/machines/Machine.d.ts +14 -0
- package/dist/query-v2/core/machines/Machine.js +33 -0
- package/dist/query-v2/core/machines/MachineError.d.ts +11 -0
- package/dist/query-v2/core/machines/MachineError.js +26 -0
- package/dist/query-v2/core/machines/MachineIdle.d.ts +8 -0
- package/dist/query-v2/core/machines/MachineIdle.js +19 -0
- package/dist/query-v2/core/machines/MachinePending.d.ts +12 -0
- package/dist/query-v2/core/machines/MachinePending.js +29 -0
- package/dist/query-v2/core/machines/MachineRefreshing.d.ts +14 -0
- package/dist/query-v2/core/machines/MachineRefreshing.js +46 -0
- package/dist/query-v2/core/machines/MachineSuccess.d.ts +16 -0
- package/dist/query-v2/core/machines/MachineSuccess.js +42 -0
- package/dist/query-v2/core/machines/MachineWithData.d.ts +18 -0
- package/dist/query-v2/core/machines/MachineWithData.js +40 -0
- package/dist/query-v2/core/machines/Patcher.d.ts +20 -0
- package/dist/query-v2/core/machines/Patcher.js +104 -0
- package/dist/query-v2/core/machines/index.d.ts +8 -0
- package/dist/query-v2/core/machines/index.js +8 -0
- package/dist/query-v2/core/resource/ResourceV2.d.ts +120 -0
- package/dist/query-v2/core/resource/ResourceV2.js +464 -0
- package/dist/query-v2/core/resource/ResourceV2Agent.d.ts +26 -0
- package/dist/query-v2/core/resource/ResourceV2Agent.js +132 -0
- package/dist/query-v2/core/resource/index.d.ts +2 -0
- package/dist/query-v2/core/resource/index.js +2 -0
- package/dist/query-v2/index.d.ts +11 -0
- package/dist/query-v2/index.js +17 -0
- package/dist/query-v2/lib/NO_VALUE.d.ts +2 -0
- package/dist/query-v2/lib/NO_VALUE.js +1 -0
- package/dist/query-v2/lib/SKIP_TOKEN.d.ts +2 -0
- package/dist/query-v2/lib/SKIP_TOKEN.js +1 -0
- package/dist/query-v2/lib/index.d.ts +4 -0
- package/dist/query-v2/lib/index.js +3 -0
- package/dist/query-v2/lib/stableStringify.d.ts +8 -0
- package/dist/query-v2/lib/stableStringify.js +23 -0
- package/dist/query-v2/plugins/ReactHooksPlugin.d.ts +25 -0
- package/dist/query-v2/plugins/ReactHooksPlugin.js +19 -0
- package/dist/query-v2/plugins/types.d.ts +1 -0
- package/dist/query-v2/plugins/types.js +1 -0
- package/dist/query-v2/react/__tests__/helpers.d.ts +12 -0
- package/dist/query-v2/react/__tests__/helpers.js +33 -0
- package/dist/query-v2/react/index.d.ts +2 -0
- package/dist/query-v2/react/index.js +2 -0
- package/dist/query-v2/react/useResourceV2Agent.d.ts +12 -0
- package/dist/query-v2/react/useResourceV2Agent.js +36 -0
- package/dist/query-v2/react/useResourceV2Ref.d.ts +12 -0
- package/dist/query-v2/react/useResourceV2Ref.js +57 -0
- package/dist/query-v2/snapshot/Snapshot.d.ts +13 -0
- package/dist/query-v2/snapshot/Snapshot.js +76 -0
- package/dist/query-v2/types/agent.types.d.ts +54 -0
- package/dist/query-v2/types/agent.types.js +1 -0
- package/dist/query-v2/types/api.types.d.ts +22 -0
- package/dist/query-v2/types/api.types.js +1 -0
- package/dist/query-v2/types/cache.types.d.ts +37 -0
- package/dist/query-v2/types/cache.types.js +1 -0
- package/dist/query-v2/types/index.d.ts +9 -0
- package/dist/query-v2/types/index.js +9 -0
- package/dist/query-v2/types/lifecycle.types.d.ts +25 -0
- package/dist/query-v2/types/lifecycle.types.js +1 -0
- package/dist/query-v2/types/machine.types.d.ts +67 -0
- package/dist/query-v2/types/machine.types.js +1 -0
- package/dist/query-v2/types/plugin.types.d.ts +38 -0
- package/dist/query-v2/types/plugin.types.js +1 -0
- package/dist/query-v2/types/resource.types.d.ts +35 -0
- package/dist/query-v2/types/resource.types.js +1 -0
- package/dist/query-v2/types/shared.types.d.ts +20 -0
- package/dist/query-v2/types/shared.types.js +1 -0
- package/dist/query-v2/types/snapshot.types.d.ts +21 -0
- package/dist/query-v2/types/snapshot.types.js +1 -0
- package/dist/signals/base/Batcher.js +9 -5
- package/dist/signals/base/ComputeCache.js +3 -3
- package/dist/signals/base/DependencyTracker.js +1 -1
- package/dist/signals/base/Devtools.d.ts +3 -2
- package/dist/signals/base/Devtools.js +54 -27
- package/dist/signals/base/Indexer.js +1 -1
- package/dist/signals/base/ReadonlySignal.js +1 -1
- package/dist/signals/base/SyncObservable.d.ts +1 -2
- package/dist/signals/base/SyncObservable.js +2 -5
- package/dist/signals/base/index.d.ts +6 -6
- package/dist/signals/base/index.js +6 -6
- package/dist/signals/index.d.ts +5 -4
- package/dist/signals/index.js +5 -4
- package/dist/signals/operators/index.d.ts +1 -1
- package/dist/signals/operators/index.js +1 -1
- package/dist/signals/react/index.d.ts +1 -1
- package/dist/signals/react/index.js +1 -1
- package/dist/signals/signals/Computed.d.ts +3 -4
- package/dist/signals/signals/Computed.js +18 -10
- package/dist/signals/signals/Effect.js +2 -1
- package/dist/signals/signals/LocalState.d.ts +3 -4
- package/dist/signals/signals/LocalState.js +8 -8
- package/dist/signals/signals/Signal.d.ts +7 -6
- package/dist/signals/signals/Signal.js +4 -1
- package/dist/signals/signals/State.d.ts +4 -5
- package/dist/signals/signals/State.js +23 -9
- package/dist/signals/signals/index.d.ts +5 -5
- package/dist/signals/signals/index.js +5 -6
- package/dist/signals/types/SignalOptions.d.ts +16 -0
- package/dist/signals/types/SignalOptions.js +1 -0
- package/dist/signals/types/index.d.ts +3 -1
- package/dist/signals/types/index.js +3 -1
- package/dist/signals/types/normalizeSignalOptions.d.ts +2 -0
- package/dist/signals/types/normalizeSignalOptions.js +10 -0
- package/dist/signals/types/signals.types.d.ts +2 -3
- package/docs/CHANGELOG.md +111 -90
- package/docs/CONTRIBUTING.md +230 -0
- package/docs/contributing/ai-assisted-development.md +47 -0
- package/docs/contributing/query-v2/README.md +379 -0
- package/docs/{release → contributing/release}/README.md +59 -59
- package/docs/devtools/README.md +228 -228
- package/docs/migrations/0.5.0.md +58 -58
- package/docs/migrations/query-v2.md +171 -0
- package/docs/options/README.md +92 -92
- package/docs/query/README.md +575 -573
- package/docs/query-v2/README.md +280 -0
- package/docs/query-v2/api-reference.md +235 -0
- package/docs/query-v2/optimistic-updates.md +148 -0
- package/docs/query-v2/ssr.md +130 -0
- package/docs/signals/README.md +300 -300
- package/docs/usage/react/README.md +309 -309
- package/package.json +86 -63
|
@@ -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,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";
|