@praxisjs/core 1.5.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 +37 -11
- 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/dist/signal/batch.d.ts.map +1 -1
- package/dist/signal/batch.js +11 -13
- package/dist/signal/batch.js.map +1 -1
- package/dist/signal/computed.d.ts +1 -1
- package/dist/signal/computed.d.ts.map +1 -1
- package/dist/signal/computed.js +46 -36
- package/dist/signal/computed.js.map +1 -1
- package/dist/signal/effect.d.ts.map +1 -1
- package/dist/signal/effect.js +11 -12
- package/dist/signal/effect.js.map +1 -1
- package/dist/signal/signal.d.ts +9 -0
- package/dist/signal/signal.d.ts.map +1 -1
- package/dist/signal/signal.js +71 -37
- package/dist/signal/signal.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/signal/batch.ts +9 -13
- package/src/signal/computed.ts +42 -39
- package/src/signal/effect.ts +11 -12
- package/src/signal/signal.ts +62 -37
package/src/signal/computed.ts
CHANGED
|
@@ -1,69 +1,72 @@
|
|
|
1
1
|
import type { Computed } from "@praxisjs/shared";
|
|
2
2
|
|
|
3
3
|
import { activeEffect, runEffect, type Effect } from "./effect";
|
|
4
|
+
import { addSub, removeSub, notifySubs, type SubList } from "./signal";
|
|
4
5
|
|
|
5
6
|
type ComputedRecompute = Effect & { __isComputedRecompute: true };
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
function isComputedRecompute(eff: Effect): boolean {
|
|
9
|
+
return (eff as Partial<ComputedRecompute>).__isComputedRecompute === true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function computed<T>(getter: () => T): Computed<T> {
|
|
13
|
+
let cachedValue: T | undefined;
|
|
9
14
|
let dirty = true;
|
|
10
|
-
|
|
15
|
+
|
|
16
|
+
const computedHolder = { subs: null as SubList };
|
|
17
|
+
const leafHolder = { subs: null as SubList };
|
|
11
18
|
let scheduled = false;
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
let recompute: ComputedRecompute | null = null;
|
|
21
|
+
|
|
22
|
+
function getRecompute(): ComputedRecompute {
|
|
23
|
+
if (recompute !== null) return recompute;
|
|
24
|
+
recompute = function markDirty(): void {
|
|
25
|
+
if (dirty) return;
|
|
15
26
|
dirty = true;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
sub(); // propagate dirty synchronously through computed chain
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
if (!scheduled) {
|
|
27
|
+
if (computedHolder.subs !== null) notifySubs(computedHolder.subs, false);
|
|
28
|
+
if (leafHolder.subs !== null && !scheduled) {
|
|
22
29
|
scheduled = true;
|
|
23
30
|
queueMicrotask(() => {
|
|
24
31
|
scheduled = false;
|
|
25
|
-
|
|
26
|
-
if (!(sub as Partial<ComputedRecompute>).__isComputedRecompute) {
|
|
27
|
-
sub(); // notify leaf effects (DOM, @Watch, subscribe)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
32
|
+
if (leafHolder.subs !== null) notifySubs(leafHolder.subs, false);
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
} as ComputedRecompute;
|
|
36
|
+
recompute.__isComputedRecompute = true;
|
|
37
|
+
return recompute;
|
|
38
|
+
}
|
|
35
39
|
|
|
36
|
-
function read() {
|
|
37
|
-
if (activeEffect) {
|
|
38
|
-
|
|
40
|
+
function read(): T {
|
|
41
|
+
if (activeEffect !== null) {
|
|
42
|
+
if (isComputedRecompute(activeEffect)) {
|
|
43
|
+
addSub(computedHolder, activeEffect);
|
|
44
|
+
} else {
|
|
45
|
+
addSub(leafHolder, activeEffect);
|
|
46
|
+
}
|
|
39
47
|
}
|
|
40
|
-
|
|
41
48
|
if (dirty) {
|
|
42
49
|
const prevEffect = activeEffect;
|
|
43
|
-
runEffect(
|
|
50
|
+
runEffect(getRecompute());
|
|
44
51
|
try {
|
|
45
|
-
cachedValue =
|
|
52
|
+
cachedValue = getter();
|
|
46
53
|
dirty = false;
|
|
47
54
|
} finally {
|
|
48
55
|
runEffect(prevEffect);
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
|
-
|
|
52
|
-
return cachedValue;
|
|
58
|
+
return cachedValue as T;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
const wrappedEffect = () => {
|
|
57
|
-
fn(read());
|
|
58
|
-
};
|
|
59
|
-
subscribers.add(wrappedEffect);
|
|
60
|
-
wrappedEffect();
|
|
61
|
-
return () => subscribers.delete(wrappedEffect);
|
|
62
|
-
}
|
|
61
|
+
const c = read as Computed<T>;
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
c.subscribe = (fn: (value: T) => void) => {
|
|
64
|
+
const wrapped: Effect = () => { fn(read()); };
|
|
65
|
+
addSub(leafHolder, wrapped);
|
|
66
|
+
wrapped();
|
|
67
|
+
return () => { removeSub(leafHolder, wrapped); };
|
|
68
|
+
};
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
c.__isComputed = true;
|
|
71
|
+
return c;
|
|
69
72
|
}
|
package/src/signal/effect.ts
CHANGED
|
@@ -4,14 +4,13 @@ export let activeEffect: Effect | null = null;
|
|
|
4
4
|
const effectStack: Effect[] = [];
|
|
5
5
|
|
|
6
6
|
export function track(effect: Effect) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
activeEffect = subscriber;
|
|
7
|
+
effectStack.push(effect);
|
|
8
|
+
activeEffect = effect;
|
|
10
9
|
try {
|
|
11
|
-
|
|
10
|
+
effect();
|
|
12
11
|
} finally {
|
|
13
12
|
effectStack.pop();
|
|
14
|
-
activeEffect = effectStack[effectStack.length - 1]
|
|
13
|
+
activeEffect = effectStack[effectStack.length - 1] ?? null;
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
16
|
|
|
@@ -36,21 +35,21 @@ export function effect(fn: () => Cleanup) {
|
|
|
36
35
|
let cleanup: Cleanup;
|
|
37
36
|
let stopped = false;
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
function run(): void {
|
|
40
39
|
if (stopped) return;
|
|
41
40
|
cleanup?.();
|
|
42
|
-
const
|
|
43
|
-
activeEffect =
|
|
41
|
+
const prev = activeEffect;
|
|
42
|
+
activeEffect = run;
|
|
44
43
|
try {
|
|
45
44
|
cleanup = fn();
|
|
46
45
|
} finally {
|
|
47
|
-
activeEffect =
|
|
46
|
+
activeEffect = prev;
|
|
48
47
|
}
|
|
49
|
-
}
|
|
48
|
+
}
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
run();
|
|
52
51
|
|
|
53
|
-
return ()
|
|
52
|
+
return function stop(): void {
|
|
54
53
|
stopped = true;
|
|
55
54
|
cleanup?.();
|
|
56
55
|
cleanup = undefined;
|
package/src/signal/signal.ts
CHANGED
|
@@ -3,53 +3,78 @@ import type { Signal } from "@praxisjs/shared";
|
|
|
3
3
|
import { isBatching, enqueueEffect } from "./batch";
|
|
4
4
|
import { activeEffect, type Effect } from "./effect";
|
|
5
5
|
|
|
6
|
+
export type SubList = Effect | Effect[] | null;
|
|
7
|
+
|
|
8
|
+
export function addSub(holder: { subs: SubList }, eff: Effect): void {
|
|
9
|
+
const subs = holder.subs;
|
|
10
|
+
if (subs === null) {
|
|
11
|
+
holder.subs = eff;
|
|
12
|
+
} else if (typeof subs === "function") {
|
|
13
|
+
if (subs !== eff) holder.subs = [subs, eff];
|
|
14
|
+
} else {
|
|
15
|
+
if (!subs.includes(eff)) subs.push(eff);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function removeSub(holder: { subs: SubList }, eff: Effect): void {
|
|
20
|
+
const subs = holder.subs;
|
|
21
|
+
if (subs === null) return;
|
|
22
|
+
if (typeof subs === "function") {
|
|
23
|
+
if (subs === eff) holder.subs = null;
|
|
24
|
+
} else {
|
|
25
|
+
const idx = subs.indexOf(eff);
|
|
26
|
+
if (idx >= 0) subs.splice(idx, 1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function notifySubs(subs: Exclude<SubList, null>, batching: boolean): void {
|
|
31
|
+
if (typeof subs === "function") {
|
|
32
|
+
if (batching) enqueueEffect(subs); else subs();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (batching) {
|
|
36
|
+
for (const sub of subs) enqueueEffect(sub);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let lastError: unknown;
|
|
40
|
+
let hasError = false;
|
|
41
|
+
for (const sub of subs) {
|
|
42
|
+
try {
|
|
43
|
+
sub();
|
|
44
|
+
} catch (e) {
|
|
45
|
+
lastError = e;
|
|
46
|
+
hasError = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (hasError) throw lastError;
|
|
50
|
+
}
|
|
51
|
+
|
|
6
52
|
export function signal<T>(initialValue: T): Signal<T> {
|
|
7
53
|
let value = initialValue;
|
|
8
|
-
const
|
|
54
|
+
const holder = { subs: null as SubList };
|
|
9
55
|
|
|
10
|
-
function read() {
|
|
11
|
-
if (activeEffect)
|
|
12
|
-
subscribers.add(activeEffect);
|
|
13
|
-
}
|
|
56
|
+
function read(): T {
|
|
57
|
+
if (activeEffect !== null) addSub(holder, activeEffect);
|
|
14
58
|
return value;
|
|
15
59
|
}
|
|
16
60
|
|
|
17
|
-
|
|
61
|
+
const s = read as Signal<T>;
|
|
62
|
+
|
|
63
|
+
s.set = (newValue: T) => {
|
|
18
64
|
if (Object.is(value, newValue)) return;
|
|
19
65
|
value = newValue;
|
|
20
|
-
if (isBatching())
|
|
21
|
-
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
const errors: unknown[] = [];
|
|
25
|
-
[...subscribers].forEach((sub) => {
|
|
26
|
-
try {
|
|
27
|
-
sub();
|
|
28
|
-
} catch (e) {
|
|
29
|
-
errors.push(e);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
if (errors.length > 0) throw errors[errors.length - 1];
|
|
33
|
-
}
|
|
66
|
+
if (holder.subs !== null) notifySubs(holder.subs, isBatching());
|
|
67
|
+
};
|
|
34
68
|
|
|
35
|
-
|
|
36
|
-
set(fn(value));
|
|
37
|
-
}
|
|
69
|
+
s.update = (fn: (prev: T) => T) => { s.set(fn(value)); };
|
|
38
70
|
|
|
39
|
-
|
|
40
|
-
const wrapped = () => {
|
|
41
|
-
|
|
42
|
-
};
|
|
43
|
-
subscribers.add(wrapped);
|
|
71
|
+
s.subscribe = (fn: (value: T) => void) => {
|
|
72
|
+
const wrapped: Effect = () => { fn(value); };
|
|
73
|
+
addSub(holder, wrapped);
|
|
44
74
|
wrapped();
|
|
45
|
-
return () =>
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const signal = read as Signal<T>;
|
|
49
|
-
signal.set = set;
|
|
50
|
-
signal.update = update;
|
|
51
|
-
signal.subscribe = subscribe;
|
|
52
|
-
signal.__isSignal = true;
|
|
75
|
+
return () => { removeSub(holder, wrapped); };
|
|
76
|
+
};
|
|
53
77
|
|
|
54
|
-
|
|
78
|
+
s.__isSignal = true;
|
|
79
|
+
return s;
|
|
55
80
|
}
|