@praxisjs/core 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/__tests__/computed.test.js +23 -0
- package/dist/__tests__/computed.test.js.map +1 -1
- package/dist/__tests__/resource-focus.test.d.ts +2 -0
- package/dist/__tests__/resource-focus.test.d.ts.map +1 -0
- package/dist/__tests__/resource-focus.test.js +50 -0
- package/dist/__tests__/resource-focus.test.js.map +1 -0
- package/dist/__tests__/resource.test.js +137 -1
- package/dist/__tests__/resource.test.js.map +1 -1
- package/dist/__tests__/signal.test.js +50 -1
- package/dist/__tests__/signal.test.js.map +1 -1
- package/dist/async/resource-cache.d.ts +21 -0
- package/dist/async/resource-cache.d.ts.map +1 -0
- package/dist/async/resource-cache.js +48 -0
- package/dist/async/resource-cache.js.map +1 -0
- package/dist/async/resource.d.ts +4 -0
- package/dist/async/resource.d.ts.map +1 -1
- package/dist/async/resource.js +74 -9
- package/dist/async/resource.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/reactive/reactive.d.ts.map +1 -1
- package/dist/reactive/reactive.js +0 -2
- package/dist/reactive/reactive.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/computed.test.ts +29 -0
- package/src/__tests__/resource-focus.test.ts +61 -0
- package/src/__tests__/resource.test.ts +178 -1
- package/src/__tests__/signal.test.ts +55 -1
- package/src/async/resource-cache.ts +62 -0
- package/src/async/resource.ts +92 -10
- package/src/internal.ts +1 -0
- package/src/reactive/reactive.ts +0 -1
package/src/async/resource.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import type { Computed, Signal } from "@praxisjs/shared";
|
|
2
2
|
|
|
3
3
|
import { computed, signal } from "../signal";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getCacheEntry,
|
|
6
|
+
setCacheEntry,
|
|
7
|
+
deleteInflight,
|
|
8
|
+
getInflight,
|
|
9
|
+
setInflight,
|
|
10
|
+
registerResource,
|
|
11
|
+
unregisterResource,
|
|
12
|
+
} from "./resource-cache";
|
|
13
|
+
import { effect, untrack } from "../signal/effect";
|
|
5
14
|
|
|
6
15
|
export type ResourceStatus = "idle" | "pending" | "success" | "error";
|
|
7
16
|
|
|
@@ -13,12 +22,16 @@ export interface Resource<T> {
|
|
|
13
22
|
refetch(): void;
|
|
14
23
|
cancel(): void;
|
|
15
24
|
mutate(data: T): void;
|
|
25
|
+
destroy(): void;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
export interface ResourceOptions<T> {
|
|
19
29
|
initialData?: T;
|
|
20
30
|
immediate?: boolean;
|
|
21
31
|
keepPreviousData?: boolean;
|
|
32
|
+
key?: string;
|
|
33
|
+
staleTime?: number;
|
|
34
|
+
refetchOnFocus?: boolean;
|
|
22
35
|
}
|
|
23
36
|
|
|
24
37
|
export function resource<T>(
|
|
@@ -29,18 +42,27 @@ export function resource<T>(
|
|
|
29
42
|
initialData = null,
|
|
30
43
|
immediate = true,
|
|
31
44
|
keepPreviousData = false,
|
|
45
|
+
key,
|
|
46
|
+
staleTime = 0,
|
|
47
|
+
refetchOnFocus = false,
|
|
32
48
|
} = options;
|
|
33
49
|
|
|
34
|
-
const
|
|
50
|
+
const cached = key ? getCacheEntry(key) : undefined;
|
|
51
|
+
const isCacheFresh =
|
|
52
|
+
cached !== undefined && Date.now() - cached.timestamp < staleTime;
|
|
53
|
+
|
|
54
|
+
const _data = signal(cached ? (cached.data as T) : initialData);
|
|
35
55
|
const _error = signal<Error | null>(null);
|
|
36
|
-
const _status = signal<ResourceStatus>("idle");
|
|
56
|
+
const _status = signal<ResourceStatus>(cached ? "success" : "idle");
|
|
37
57
|
|
|
38
58
|
let _runId = 0;
|
|
39
59
|
|
|
40
|
-
function _execute(fn: Promise<T>) {
|
|
60
|
+
function _execute(fn: Promise<T>): void {
|
|
41
61
|
const currentRunId = ++_runId;
|
|
42
62
|
|
|
43
|
-
|
|
63
|
+
// untrack: _data.set(result) in the resolve handler must not re-trigger the enclosing effect.
|
|
64
|
+
const hasCachedData = key !== undefined && untrack(() => _data()) !== null;
|
|
65
|
+
if (!keepPreviousData && !hasCachedData) {
|
|
44
66
|
_data.set(null);
|
|
45
67
|
}
|
|
46
68
|
_error.set(null);
|
|
@@ -51,16 +73,36 @@ export function resource<T>(
|
|
|
51
73
|
_data.set(result);
|
|
52
74
|
_error.set(null);
|
|
53
75
|
_status.set("success");
|
|
76
|
+
if (key) {
|
|
77
|
+
setCacheEntry(key, result);
|
|
78
|
+
deleteInflight(key);
|
|
79
|
+
}
|
|
54
80
|
}).catch((err: unknown) => {
|
|
55
81
|
if (currentRunId !== _runId) return;
|
|
56
82
|
_error.set(err instanceof Error ? err : new Error(String(err)));
|
|
57
83
|
_status.set("error");
|
|
84
|
+
if (key) deleteInflight(key);
|
|
58
85
|
});
|
|
59
86
|
}
|
|
60
87
|
|
|
61
|
-
function
|
|
62
|
-
|
|
88
|
+
function _startFetch(): void {
|
|
89
|
+
if (key) {
|
|
90
|
+
const existing = getInflight(key);
|
|
91
|
+
if (existing) {
|
|
92
|
+
_execute(existing as Promise<T>);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const promise = fetcher();
|
|
96
|
+
setInflight(key, promise as Promise<unknown>);
|
|
97
|
+
_execute(promise);
|
|
98
|
+
} else {
|
|
63
99
|
_execute(fetcher());
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function execute(): void {
|
|
104
|
+
try {
|
|
105
|
+
_startFetch();
|
|
64
106
|
} catch (err: unknown) {
|
|
65
107
|
_runId++;
|
|
66
108
|
_error.set(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -68,17 +110,56 @@ export function resource<T>(
|
|
|
68
110
|
}
|
|
69
111
|
}
|
|
70
112
|
|
|
71
|
-
if (
|
|
72
|
-
|
|
113
|
+
if (key) {
|
|
114
|
+
registerResource(key, execute);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let stopEffect: (() => void) | undefined;
|
|
118
|
+
|
|
119
|
+
if (immediate && !isCacheFresh) {
|
|
120
|
+
stopEffect = effect(() => {
|
|
73
121
|
try {
|
|
74
|
-
|
|
122
|
+
if (key) {
|
|
123
|
+
const existing = getInflight(key);
|
|
124
|
+
if (existing) {
|
|
125
|
+
_execute(existing as Promise<T>);
|
|
126
|
+
} else {
|
|
127
|
+
const promise = fetcher();
|
|
128
|
+
setInflight(key, promise as Promise<unknown>);
|
|
129
|
+
_execute(promise);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
_execute(fetcher());
|
|
133
|
+
}
|
|
75
134
|
} catch (err: unknown) {
|
|
135
|
+
_runId++;
|
|
76
136
|
_error.set(err instanceof Error ? err : new Error(String(err)));
|
|
77
137
|
_status.set("error");
|
|
78
138
|
}
|
|
79
139
|
});
|
|
80
140
|
}
|
|
81
141
|
|
|
142
|
+
let _removeFocusListener: (() => void) | undefined;
|
|
143
|
+
if (refetchOnFocus && typeof document !== "undefined") {
|
|
144
|
+
const onVisibility = (): void => {
|
|
145
|
+
if (document.visibilityState === "visible") {
|
|
146
|
+
execute();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
150
|
+
_removeFocusListener = (): void => {
|
|
151
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function destroy(): void {
|
|
156
|
+
stopEffect?.();
|
|
157
|
+
stopEffect = undefined;
|
|
158
|
+
_removeFocusListener?.();
|
|
159
|
+
_removeFocusListener = undefined;
|
|
160
|
+
if (key) unregisterResource(key, execute);
|
|
161
|
+
}
|
|
162
|
+
|
|
82
163
|
return {
|
|
83
164
|
data: computed(() => _data()),
|
|
84
165
|
pending: computed(() => _status() === "pending"),
|
|
@@ -97,6 +178,7 @@ export function resource<T>(
|
|
|
97
178
|
_error.set(null);
|
|
98
179
|
_status.set("success");
|
|
99
180
|
},
|
|
181
|
+
destroy,
|
|
100
182
|
};
|
|
101
183
|
}
|
|
102
184
|
|
package/src/internal.ts
CHANGED
package/src/reactive/reactive.ts
CHANGED