@praxisjs/core 1.7.0 → 1.8.1
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 +48 -0
- package/dist/__tests__/computed.test.js +54 -1
- package/dist/__tests__/computed.test.js.map +1 -1
- package/dist/__tests__/effect.test.js +68 -1
- package/dist/__tests__/effect.test.js.map +1 -1
- package/dist/__tests__/resource.test.js +28 -0
- package/dist/__tests__/resource.test.js.map +1 -1
- package/dist/__tests__/signal.test.js +13 -0
- package/dist/__tests__/signal.test.js.map +1 -1
- package/dist/async/resource.d.ts.map +1 -1
- package/dist/async/resource.js +8 -3
- package/dist/async/resource.js.map +1 -1
- package/dist/component/base.d.ts +1 -1
- package/dist/component/base.d.ts.map +1 -1
- package/dist/component/base.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/signal/computed.d.ts +2 -1
- package/dist/signal/computed.d.ts.map +1 -1
- package/dist/signal/computed.js +11 -2
- package/dist/signal/computed.js.map +1 -1
- package/dist/signal/effect.d.ts +6 -0
- package/dist/signal/effect.d.ts.map +1 -1
- package/dist/signal/effect.js +34 -0
- package/dist/signal/effect.js.map +1 -1
- package/dist/signal/index.d.ts +1 -1
- package/dist/signal/index.d.ts.map +1 -1
- package/dist/signal/index.js +1 -1
- package/dist/signal/index.js.map +1 -1
- package/dist/signal/signal.d.ts +4 -8
- package/dist/signal/signal.d.ts.map +1 -1
- package/dist/signal/signal.js +7 -3
- package/dist/signal/signal.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/computed.test.ts +64 -1
- package/src/__tests__/effect.test.ts +92 -1
- package/src/__tests__/resource.test.ts +37 -0
- package/src/__tests__/signal.test.ts +19 -0
- package/src/async/resource.ts +7 -2
- package/src/component/base.ts +2 -1
- package/src/internal.ts +1 -0
- package/src/signal/computed.ts +14 -4
- package/src/signal/effect.ts +36 -0
- package/src/signal/index.ts +1 -1
- package/src/signal/signal.ts +15 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAWnE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CA8DxD;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAMpG"}
|
package/dist/signal/computed.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { activeEffect, runEffect } from "./effect";
|
|
1
|
+
import { activeEffect, cleanupEffectDeps, runEffect } from "./effect";
|
|
2
2
|
import { addSub, removeSub, notifySubs } from "./signal";
|
|
3
3
|
function isComputedRecompute(eff) {
|
|
4
4
|
return eff.__isComputedRecompute === true;
|
|
@@ -42,7 +42,9 @@ export function computed(getter) {
|
|
|
42
42
|
}
|
|
43
43
|
if (dirty) {
|
|
44
44
|
const prevEffect = activeEffect;
|
|
45
|
-
|
|
45
|
+
const recomputeEffect = getRecompute();
|
|
46
|
+
cleanupEffectDeps(recomputeEffect);
|
|
47
|
+
runEffect(recomputeEffect);
|
|
46
48
|
try {
|
|
47
49
|
cachedValue = getter();
|
|
48
50
|
dirty = false;
|
|
@@ -63,4 +65,11 @@ export function computed(getter) {
|
|
|
63
65
|
c.__isComputed = true;
|
|
64
66
|
return c;
|
|
65
67
|
}
|
|
68
|
+
export function writableComputed(getter, setter) {
|
|
69
|
+
const c = computed(getter);
|
|
70
|
+
c.set = setter;
|
|
71
|
+
c.update = (updater) => { setter(updater(c())); };
|
|
72
|
+
c.__isWritableComputed = true;
|
|
73
|
+
return c;
|
|
74
|
+
}
|
|
66
75
|
//# sourceMappingURL=computed.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computed.js","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"computed.js","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"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"}
|
package/dist/signal/effect.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export type Effect = () => void;
|
|
2
|
+
export type SubList = Effect | Effect[] | null;
|
|
3
|
+
export interface SubscriberHolder {
|
|
4
|
+
subs: SubList;
|
|
5
|
+
}
|
|
2
6
|
export declare let activeEffect: Effect | null;
|
|
7
|
+
export declare function recordDependency(eff: Effect, holder: SubscriberHolder): void;
|
|
8
|
+
export declare function cleanupEffectDeps(eff: Effect): void;
|
|
3
9
|
export declare function track(effect: Effect): void;
|
|
4
10
|
export declare function runEffect(effect: Effect | null): void;
|
|
5
11
|
export declare function untrack<T>(fn: () => T): T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../../src/signal/effect.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../../src/signal/effect.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC;AAChC,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;CACf;AAID,eAAO,IAAI,YAAY,EAAE,MAAM,GAAG,IAAW,CAAC;AAe9C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAI5E;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAMnD;AAED,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,QAUnC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,QAE9C;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAQzC;AAGD,KAAK,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;AAEnC,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,OAAO,SAmBd,IAAI,CAO7B"}
|
package/dist/signal/effect.js
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
export let activeEffect = null;
|
|
2
2
|
const effectStack = [];
|
|
3
|
+
function removeTrackedSub(holder, eff) {
|
|
4
|
+
const subs = holder.subs;
|
|
5
|
+
if (subs === null)
|
|
6
|
+
return;
|
|
7
|
+
if (typeof subs === "function") {
|
|
8
|
+
if (subs === eff)
|
|
9
|
+
holder.subs = null;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const idx = subs.indexOf(eff);
|
|
13
|
+
if (idx >= 0)
|
|
14
|
+
subs.splice(idx, 1);
|
|
15
|
+
if (subs.length === 0)
|
|
16
|
+
holder.subs = null;
|
|
17
|
+
}
|
|
18
|
+
export function recordDependency(eff, holder) {
|
|
19
|
+
const tracked = eff;
|
|
20
|
+
tracked.__deps ??= new Set();
|
|
21
|
+
tracked.__deps.add(holder);
|
|
22
|
+
}
|
|
23
|
+
export function cleanupEffectDeps(eff) {
|
|
24
|
+
const tracked = eff;
|
|
25
|
+
const deps = tracked.__deps;
|
|
26
|
+
if (!deps)
|
|
27
|
+
return;
|
|
28
|
+
for (const holder of deps)
|
|
29
|
+
removeTrackedSub(holder, eff);
|
|
30
|
+
deps.clear();
|
|
31
|
+
}
|
|
3
32
|
export function track(effect) {
|
|
33
|
+
cleanupEffectDeps(effect);
|
|
4
34
|
effectStack.push(effect);
|
|
5
35
|
activeEffect = effect;
|
|
6
36
|
try {
|
|
@@ -30,6 +60,7 @@ export function effect(fn) {
|
|
|
30
60
|
function run() {
|
|
31
61
|
if (stopped)
|
|
32
62
|
return;
|
|
63
|
+
cleanupEffectDeps(run);
|
|
33
64
|
cleanup?.();
|
|
34
65
|
const prev = activeEffect;
|
|
35
66
|
activeEffect = run;
|
|
@@ -42,7 +73,10 @@ export function effect(fn) {
|
|
|
42
73
|
}
|
|
43
74
|
run();
|
|
44
75
|
return function stop() {
|
|
76
|
+
if (stopped)
|
|
77
|
+
return;
|
|
45
78
|
stopped = true;
|
|
79
|
+
cleanupEffectDeps(run);
|
|
46
80
|
cleanup?.();
|
|
47
81
|
cleanup = undefined;
|
|
48
82
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../src/signal/effect.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../src/signal/effect.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,IAAI,YAAY,GAAkB,IAAI,CAAC;AAC9C,MAAM,WAAW,GAAa,EAAE,CAAC;AAEjC,SAAS,gBAAgB,CAAC,MAAwB,EAAE,GAAW;IAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO;IAC1B,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,IAAI,IAAI,KAAK,GAAG;YAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;QACrC,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,GAAG,IAAI,CAAC;QAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,MAAwB;IACpE,MAAM,OAAO,GAAG,GAAoB,CAAC;IACrC,OAAO,CAAC,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;IAC7B,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,OAAO,GAAG,GAAoB,CAAC;IACrC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAC5B,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,KAAK,MAAM,MAAM,IAAI,IAAI;QAAE,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzD,IAAI,CAAC,KAAK,EAAE,CAAC;AACf,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,MAAc;IAClC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzB,YAAY,GAAG,MAAM,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,EAAE,CAAC;IACX,CAAC;YAAS,CAAC;QACT,WAAW,CAAC,GAAG,EAAE,CAAC;QAClB,YAAY,GAAG,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;IAC7D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAqB;IAC7C,YAAY,GAAG,MAAM,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,OAAO,CAAI,EAAW;IACpC,MAAM,IAAI,GAAG,YAAY,CAAC;IAC1B,YAAY,GAAG,IAAI,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;AACH,CAAC;AAKD,MAAM,UAAU,MAAM,CAAC,EAAiB;IACtC,IAAI,OAAgB,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,SAAS,GAAG;QACV,IAAI,OAAO;YAAE,OAAO;QACpB,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACvB,OAAO,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,YAAY,CAAC;QAC1B,YAAY,GAAG,GAAG,CAAC;QACnB,IAAI,CAAC;YACH,OAAO,GAAG,EAAE,EAAE,CAAC;QACjB,CAAC;gBAAS,CAAC;YACT,YAAY,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,GAAG,EAAE,CAAC;IAEN,OAAO,SAAS,IAAI;QAClB,IAAI,OAAO;YAAE,OAAO;QACpB,OAAO,GAAG,IAAI,CAAC;QACf,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACvB,OAAO,EAAE,EAAE,CAAC;QACZ,OAAO,GAAG,SAAS,CAAC;IACtB,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/signal/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { batch } from "./batch";
|
|
2
2
|
export { peek } from "./peek";
|
|
3
3
|
export { signal } from "./signal";
|
|
4
|
-
export { computed } from "./computed";
|
|
4
|
+
export { computed, writableComputed } from "./computed";
|
|
5
5
|
export { type PersistedSignalOptions, persistedSignal } from "./persisted";
|
|
6
6
|
export { type SyncedSignal, syncedSignal } from "./synced";
|
|
7
7
|
export { effect, untrack } from "./effect";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,KAAK,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/signal/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { batch } from "./batch";
|
|
2
2
|
export { peek } from "./peek";
|
|
3
3
|
export { signal } from "./signal";
|
|
4
|
-
export { computed } from "./computed";
|
|
4
|
+
export { computed, writableComputed } from "./computed";
|
|
5
5
|
export { persistedSignal } from "./persisted";
|
|
6
6
|
export { syncedSignal } from "./synced";
|
|
7
7
|
export { effect, untrack } from "./effect";
|
package/dist/signal/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/signal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/signal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAA+B,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAqB,YAAY,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/signal/signal.d.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { Signal } from "@praxisjs/shared";
|
|
2
|
-
import { type Effect } from "./effect";
|
|
3
|
-
export type SubList
|
|
4
|
-
export declare function addSub(holder:
|
|
5
|
-
|
|
6
|
-
}, eff: Effect): void;
|
|
7
|
-
export declare function removeSub(holder: {
|
|
8
|
-
subs: SubList;
|
|
9
|
-
}, eff: Effect): void;
|
|
2
|
+
import { type Effect, type SubList, type SubscriberHolder } from "./effect";
|
|
3
|
+
export type { SubList } from "./effect";
|
|
4
|
+
export declare function addSub(holder: SubscriberHolder, eff: Effect): void;
|
|
5
|
+
export declare function removeSub(holder: SubscriberHolder, eff: Effect): void;
|
|
10
6
|
export declare function notifySubs(subs: Exclude<SubList, null>, batching: boolean): void;
|
|
11
7
|
export declare function signal<T>(initialValue: T): Signal<T>;
|
|
12
8
|
//# sourceMappingURL=signal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,
|
|
1
|
+
{"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,OAAO,EACZ,KAAK,gBAAgB,EACtB,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,wBAAgB,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAUlE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAUrE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAqBhF;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CA4BpD"}
|
package/dist/signal/signal.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isBatching, enqueueEffect } from "./batch";
|
|
2
|
-
import { activeEffect } from "./effect";
|
|
2
|
+
import { activeEffect, recordDependency, } from "./effect";
|
|
3
3
|
export function addSub(holder, eff) {
|
|
4
4
|
const subs = holder.subs;
|
|
5
5
|
if (subs === null) {
|
|
@@ -13,6 +13,7 @@ export function addSub(holder, eff) {
|
|
|
13
13
|
if (!subs.includes(eff))
|
|
14
14
|
subs.push(eff);
|
|
15
15
|
}
|
|
16
|
+
recordDependency(eff, holder);
|
|
16
17
|
}
|
|
17
18
|
export function removeSub(holder, eff) {
|
|
18
19
|
const subs = holder.subs;
|
|
@@ -26,6 +27,8 @@ export function removeSub(holder, eff) {
|
|
|
26
27
|
const idx = subs.indexOf(eff);
|
|
27
28
|
if (idx >= 0)
|
|
28
29
|
subs.splice(idx, 1);
|
|
30
|
+
if (subs.length === 0)
|
|
31
|
+
holder.subs = null;
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
export function notifySubs(subs, batching) {
|
|
@@ -36,14 +39,15 @@ export function notifySubs(subs, batching) {
|
|
|
36
39
|
subs();
|
|
37
40
|
return;
|
|
38
41
|
}
|
|
42
|
+
const snapshot = [...subs];
|
|
39
43
|
if (batching) {
|
|
40
|
-
for (const sub of
|
|
44
|
+
for (const sub of snapshot)
|
|
41
45
|
enqueueEffect(sub);
|
|
42
46
|
return;
|
|
43
47
|
}
|
|
44
48
|
let lastError;
|
|
45
49
|
let hasError = false;
|
|
46
|
-
for (const sub of
|
|
50
|
+
for (const sub of snapshot) {
|
|
47
51
|
try {
|
|
48
52
|
sub();
|
|
49
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,
|
|
1
|
+
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"typescript": "^5.9.3"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@praxisjs/shared": "0.
|
|
21
|
+
"@praxisjs/shared": "0.3.0"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsc",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
2
|
|
|
3
|
-
import { computed } from "../signal/computed";
|
|
3
|
+
import { computed, writableComputed } from "../signal/computed";
|
|
4
4
|
import { signal } from "../signal/signal";
|
|
5
5
|
|
|
6
6
|
describe("computed", () => {
|
|
@@ -131,6 +131,27 @@ describe("computed", () => {
|
|
|
131
131
|
expect(c()).toBe(21);
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
+
it("dynamic dependency: stale branch changes do not dirty the computed", () => {
|
|
135
|
+
const toggle = signal(true);
|
|
136
|
+
const a = signal(10);
|
|
137
|
+
const b = signal(20);
|
|
138
|
+
const fn = vi.fn(() => (toggle() ? a() : b()));
|
|
139
|
+
const c = computed(fn);
|
|
140
|
+
|
|
141
|
+
expect(c()).toBe(10);
|
|
142
|
+
toggle.set(false);
|
|
143
|
+
expect(c()).toBe(20);
|
|
144
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
145
|
+
|
|
146
|
+
a.set(11);
|
|
147
|
+
expect(c()).toBe(20);
|
|
148
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
149
|
+
|
|
150
|
+
b.set(21);
|
|
151
|
+
expect(c()).toBe(21);
|
|
152
|
+
expect(fn).toHaveBeenCalledTimes(3);
|
|
153
|
+
});
|
|
154
|
+
|
|
134
155
|
it("unsubscribe then re-subscribe works correctly", async () => {
|
|
135
156
|
const s = signal(1);
|
|
136
157
|
const c = computed(() => s() * 3);
|
|
@@ -225,3 +246,45 @@ describe("computed", () => {
|
|
|
225
246
|
}).not.toThrow();
|
|
226
247
|
});
|
|
227
248
|
});
|
|
249
|
+
|
|
250
|
+
describe("writableComputed", () => {
|
|
251
|
+
it("read returns the getter value", () => {
|
|
252
|
+
const s = signal(5);
|
|
253
|
+
const wc = writableComputed(() => s() * 2, (_v) => { /* no-op */ });
|
|
254
|
+
expect(wc()).toBe(10);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("set calls the setter", () => {
|
|
258
|
+
const s = signal(0);
|
|
259
|
+
const wc = writableComputed(() => s(), (v) => { s.set(v); });
|
|
260
|
+
wc.set(42);
|
|
261
|
+
expect(s()).toBe(42);
|
|
262
|
+
expect(wc()).toBe(42);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("update applies the updater and calls the setter", () => {
|
|
266
|
+
const s = signal(10);
|
|
267
|
+
const wc = writableComputed(() => s(), (v) => { s.set(v); });
|
|
268
|
+
wc.update((current) => current + 5);
|
|
269
|
+
expect(s()).toBe(15);
|
|
270
|
+
expect(wc()).toBe(15);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("marks __isWritableComputed = true", () => {
|
|
274
|
+
const wc = writableComputed(() => 1, (_v) => { /* no-op */ });
|
|
275
|
+
expect(wc.__isWritableComputed).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("inherits __isComputed = true from computed", () => {
|
|
279
|
+
const wc = writableComputed(() => 1, (_v) => { /* no-op */ });
|
|
280
|
+
expect(wc.__isComputed).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("recomputes when underlying signal changes", () => {
|
|
284
|
+
const s = signal(3);
|
|
285
|
+
const wc = writableComputed(() => s() * 2, (_v) => { /* no-op */ });
|
|
286
|
+
expect(wc()).toBe(6);
|
|
287
|
+
s.set(7);
|
|
288
|
+
expect(wc()).toBe(14);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
effect,
|
|
5
|
+
track,
|
|
6
|
+
activeEffect,
|
|
7
|
+
cleanupEffectDeps,
|
|
8
|
+
recordDependency,
|
|
9
|
+
type SubscriberHolder,
|
|
10
|
+
} from "../signal/effect";
|
|
11
|
+
import { batch } from "../signal/batch";
|
|
4
12
|
import { signal } from "../signal/signal";
|
|
5
13
|
|
|
6
14
|
describe("effect", () => {
|
|
@@ -63,6 +71,50 @@ describe("effect", () => {
|
|
|
63
71
|
expect(values).toEqual([0]); // only initial run
|
|
64
72
|
});
|
|
65
73
|
|
|
74
|
+
it("drops dependencies that are no longer read", () => {
|
|
75
|
+
const useA = signal(true);
|
|
76
|
+
const a = signal(1);
|
|
77
|
+
const b = signal(10);
|
|
78
|
+
const values: number[] = [];
|
|
79
|
+
|
|
80
|
+
effect(() => {
|
|
81
|
+
values.push(useA() ? a() : b());
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
useA.set(false);
|
|
85
|
+
expect(values).toEqual([1, 10]);
|
|
86
|
+
|
|
87
|
+
a.set(2);
|
|
88
|
+
expect(values).toEqual([1, 10]);
|
|
89
|
+
|
|
90
|
+
b.set(20);
|
|
91
|
+
expect(values).toEqual([1, 10, 20]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("does not skip other subscribers when one effect changes subscriptions while notifying", () => {
|
|
95
|
+
const useOther = signal(false);
|
|
96
|
+
const source = signal(0);
|
|
97
|
+
const other = signal(100);
|
|
98
|
+
const first = vi.fn();
|
|
99
|
+
const second = vi.fn();
|
|
100
|
+
|
|
101
|
+
effect(() => {
|
|
102
|
+
first(useOther() ? other() : source());
|
|
103
|
+
});
|
|
104
|
+
effect(() => {
|
|
105
|
+
second(source());
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
useOther.set(true);
|
|
109
|
+
first.mockClear();
|
|
110
|
+
second.mockClear();
|
|
111
|
+
|
|
112
|
+
source.set(1);
|
|
113
|
+
|
|
114
|
+
expect(first).not.toHaveBeenCalled();
|
|
115
|
+
expect(second).toHaveBeenCalledWith(1);
|
|
116
|
+
});
|
|
117
|
+
|
|
66
118
|
it("effect with no reactive dependencies runs once only", () => {
|
|
67
119
|
const ran = vi.fn();
|
|
68
120
|
effect(() => {
|
|
@@ -103,6 +155,45 @@ describe("effect", () => {
|
|
|
103
155
|
expect(cleanupFn).toHaveBeenCalledTimes(1);
|
|
104
156
|
});
|
|
105
157
|
|
|
158
|
+
it("ignores a queued run after the effect was stopped", () => {
|
|
159
|
+
const s = signal(0);
|
|
160
|
+
const values: number[] = [];
|
|
161
|
+
const stop = effect(() => {
|
|
162
|
+
values.push(s());
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
batch(() => {
|
|
166
|
+
s.set(1);
|
|
167
|
+
stop();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(values).toEqual([0]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("cleanupEffectDeps handles empty, single, and array dependency holders", () => {
|
|
174
|
+
const tracked = vi.fn();
|
|
175
|
+
const other = vi.fn();
|
|
176
|
+
const emptyHolder: SubscriberHolder = { subs: null };
|
|
177
|
+
const singleHolder: SubscriberHolder = { subs: tracked };
|
|
178
|
+
const arrayHolder: SubscriberHolder = { subs: [tracked, other] };
|
|
179
|
+
const unmatchedSingleHolder: SubscriberHolder = { subs: other };
|
|
180
|
+
const unmatchedArrayHolder: SubscriberHolder = { subs: [other] };
|
|
181
|
+
|
|
182
|
+
recordDependency(tracked, emptyHolder);
|
|
183
|
+
recordDependency(tracked, singleHolder);
|
|
184
|
+
recordDependency(tracked, arrayHolder);
|
|
185
|
+
recordDependency(tracked, unmatchedSingleHolder);
|
|
186
|
+
recordDependency(tracked, unmatchedArrayHolder);
|
|
187
|
+
|
|
188
|
+
cleanupEffectDeps(tracked);
|
|
189
|
+
|
|
190
|
+
expect(emptyHolder.subs).toBeNull();
|
|
191
|
+
expect(singleHolder.subs).toBeNull();
|
|
192
|
+
expect(arrayHolder.subs).toEqual([other]);
|
|
193
|
+
expect(unmatchedSingleHolder.subs).toBe(other);
|
|
194
|
+
expect(unmatchedArrayHolder.subs).toEqual([other]);
|
|
195
|
+
});
|
|
196
|
+
|
|
106
197
|
it("exception inside nested track() restores outer activeEffect", () => {
|
|
107
198
|
const s = signal(0);
|
|
108
199
|
const outer = vi.fn();
|
|
@@ -218,6 +218,22 @@ describe("resource — cache + SWR (key, staleTime)", () => {
|
|
|
218
218
|
r1.destroy();
|
|
219
219
|
r2.destroy();
|
|
220
220
|
});
|
|
221
|
+
|
|
222
|
+
it("key: mutate() updates the shared cache for future resources", async () => {
|
|
223
|
+
const fetcher = vi.fn(() => Promise.resolve("server-value"));
|
|
224
|
+
const r1 = resource(fetcher, { key: "mutate-cache", staleTime: 60_000 });
|
|
225
|
+
await vi.waitFor(() => r1.status() === "success");
|
|
226
|
+
|
|
227
|
+
r1.mutate("optimistic-value");
|
|
228
|
+
|
|
229
|
+
const r2 = resource(fetcher, { key: "mutate-cache", staleTime: 60_000 });
|
|
230
|
+
expect(r2.data()).toBe("optimistic-value");
|
|
231
|
+
expect(r2.status()).toBe("success");
|
|
232
|
+
await new Promise((res) => setTimeout(res, 10));
|
|
233
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
234
|
+
r1.destroy();
|
|
235
|
+
r2.destroy();
|
|
236
|
+
});
|
|
221
237
|
});
|
|
222
238
|
|
|
223
239
|
describe("resource — deduplication", () => {
|
|
@@ -243,6 +259,27 @@ describe("resource — deduplication", () => {
|
|
|
243
259
|
r1.destroy();
|
|
244
260
|
r2.destroy();
|
|
245
261
|
});
|
|
262
|
+
|
|
263
|
+
it("key: cancel() clears the settled in-flight request even when the local result is stale", async () => {
|
|
264
|
+
let resolveFirst!: (v: string) => void;
|
|
265
|
+
const fetcher = vi.fn(
|
|
266
|
+
() => new Promise<string>((res) => { resolveFirst = res; }),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const r1 = resource(fetcher, { key: "cancel-inflight" });
|
|
270
|
+
r1.cancel();
|
|
271
|
+
resolveFirst("stale");
|
|
272
|
+
await new Promise((res) => setTimeout(res, 0));
|
|
273
|
+
|
|
274
|
+
const r2 = resource(() => Promise.resolve("fresh"), { key: "cancel-inflight" });
|
|
275
|
+
await vi.waitFor(() => r2.data() === "fresh");
|
|
276
|
+
|
|
277
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
278
|
+
expect(r1.data()).toBeNull();
|
|
279
|
+
expect(r2.data()).toBe("fresh");
|
|
280
|
+
r1.destroy();
|
|
281
|
+
r2.destroy();
|
|
282
|
+
});
|
|
246
283
|
});
|
|
247
284
|
|
|
248
285
|
describe("resource — key + non-Error sync throw in effect", () => {
|
|
@@ -179,6 +179,25 @@ describe("signal", () => {
|
|
|
179
179
|
expect(holder.subs).toBe(fn1);
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
+
it("addSub does not add a duplicate subscriber to an array holder", () => {
|
|
183
|
+
const fn1: Effect = () => {};
|
|
184
|
+
const fn2: Effect = () => {};
|
|
185
|
+
const holder: { subs: SubList } = { subs: [fn1, fn2] };
|
|
186
|
+
|
|
187
|
+
addSub(holder, fn2);
|
|
188
|
+
|
|
189
|
+
expect(holder.subs).toEqual([fn1, fn2]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("removeSub normalizes an emptied array holder back to null", () => {
|
|
193
|
+
const fn: Effect = () => {};
|
|
194
|
+
const holder: { subs: SubList } = { subs: [fn] };
|
|
195
|
+
|
|
196
|
+
removeSub(holder, fn);
|
|
197
|
+
|
|
198
|
+
expect(holder.subs).toBeNull();
|
|
199
|
+
});
|
|
200
|
+
|
|
182
201
|
it("set() with mutated object reference (same ref) does NOT notify — Object.is semantics", () => {
|
|
183
202
|
const obj = { count: 0 };
|
|
184
203
|
const s = signal(obj);
|
package/src/async/resource.ts
CHANGED
|
@@ -60,6 +60,10 @@ export function resource<T>(
|
|
|
60
60
|
function _execute(fn: Promise<T>): void {
|
|
61
61
|
const currentRunId = ++_runId;
|
|
62
62
|
|
|
63
|
+
function clearInflight(): void {
|
|
64
|
+
if (key && getInflight(key) === fn) deleteInflight(key);
|
|
65
|
+
}
|
|
66
|
+
|
|
63
67
|
// untrack: _data.set(result) in the resolve handler must not re-trigger the enclosing effect.
|
|
64
68
|
const hasCachedData = key !== undefined && untrack(() => _data()) !== null;
|
|
65
69
|
if (!keepPreviousData && !hasCachedData) {
|
|
@@ -69,19 +73,19 @@ export function resource<T>(
|
|
|
69
73
|
_status.set("pending");
|
|
70
74
|
|
|
71
75
|
fn.then((result) => {
|
|
76
|
+
clearInflight();
|
|
72
77
|
if (currentRunId !== _runId) return;
|
|
73
78
|
_data.set(result);
|
|
74
79
|
_error.set(null);
|
|
75
80
|
_status.set("success");
|
|
76
81
|
if (key) {
|
|
77
82
|
setCacheEntry(key, result);
|
|
78
|
-
deleteInflight(key);
|
|
79
83
|
}
|
|
80
84
|
}).catch((err: unknown) => {
|
|
85
|
+
clearInflight();
|
|
81
86
|
if (currentRunId !== _runId) return;
|
|
82
87
|
_error.set(err instanceof Error ? err : new Error(String(err)));
|
|
83
88
|
_status.set("error");
|
|
84
|
-
if (key) deleteInflight(key);
|
|
85
89
|
});
|
|
86
90
|
}
|
|
87
91
|
|
|
@@ -177,6 +181,7 @@ export function resource<T>(
|
|
|
177
181
|
_data.set(data);
|
|
178
182
|
_error.set(null);
|
|
179
183
|
_status.set("success");
|
|
184
|
+
if (key) setCacheEntry(key, data);
|
|
180
185
|
},
|
|
181
186
|
destroy,
|
|
182
187
|
};
|
package/src/component/base.ts
CHANGED
|
@@ -19,7 +19,8 @@ export abstract class RootComponent<T extends object = Record<string, never>> {
|
|
|
19
19
|
onBeforeMount?(): void;
|
|
20
20
|
onMount?(): void;
|
|
21
21
|
onUnmount?(): void;
|
|
22
|
-
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
|
23
|
+
onError?(error: Error): Node | Node[] | null | undefined | void;
|
|
23
24
|
|
|
24
25
|
abstract render(): Node | Node[] | null;
|
|
25
26
|
}
|
package/src/internal.ts
CHANGED
package/src/signal/computed.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Computed } from "@praxisjs/shared";
|
|
1
|
+
import type { Computed, WritableComputed } from "@praxisjs/shared";
|
|
2
2
|
|
|
3
|
-
import { activeEffect, runEffect, type Effect } from "./effect";
|
|
4
|
-
import { addSub, removeSub, notifySubs
|
|
3
|
+
import { activeEffect, cleanupEffectDeps, runEffect, type Effect, type SubList } from "./effect";
|
|
4
|
+
import { addSub, removeSub, notifySubs } from "./signal";
|
|
5
5
|
|
|
6
6
|
type ComputedRecompute = Effect & { __isComputedRecompute: true };
|
|
7
7
|
|
|
@@ -47,7 +47,9 @@ export function computed<T>(getter: () => T): Computed<T> {
|
|
|
47
47
|
}
|
|
48
48
|
if (dirty) {
|
|
49
49
|
const prevEffect = activeEffect;
|
|
50
|
-
|
|
50
|
+
const recomputeEffect = getRecompute();
|
|
51
|
+
cleanupEffectDeps(recomputeEffect);
|
|
52
|
+
runEffect(recomputeEffect);
|
|
51
53
|
try {
|
|
52
54
|
cachedValue = getter();
|
|
53
55
|
dirty = false;
|
|
@@ -70,3 +72,11 @@ export function computed<T>(getter: () => T): Computed<T> {
|
|
|
70
72
|
c.__isComputed = true;
|
|
71
73
|
return c;
|
|
72
74
|
}
|
|
75
|
+
|
|
76
|
+
export function writableComputed<T>(getter: () => T, setter: (value: T) => void): WritableComputed<T> {
|
|
77
|
+
const c = computed(getter) as WritableComputed<T>;
|
|
78
|
+
c.set = setter;
|
|
79
|
+
c.update = (updater) => { setter(updater(c())); };
|
|
80
|
+
c.__isWritableComputed = true;
|
|
81
|
+
return c;
|
|
82
|
+
}
|