@praxisjs/core 1.8.0 → 1.8.2

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 (47) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/__tests__/computed.test.js +17 -0
  3. package/dist/__tests__/computed.test.js.map +1 -1
  4. package/dist/__tests__/effect.test.js +68 -1
  5. package/dist/__tests__/effect.test.js.map +1 -1
  6. package/dist/__tests__/resource.test.js +28 -0
  7. package/dist/__tests__/resource.test.js.map +1 -1
  8. package/dist/__tests__/signal.test.js +13 -0
  9. package/dist/__tests__/signal.test.js.map +1 -1
  10. package/dist/async/resource.d.ts.map +1 -1
  11. package/dist/async/resource.js +8 -3
  12. package/dist/async/resource.js.map +1 -1
  13. package/dist/component/base.d.ts +17 -3
  14. package/dist/component/base.d.ts.map +1 -1
  15. package/dist/component/base.js +18 -2
  16. package/dist/component/base.js.map +1 -1
  17. package/dist/component/stateful.d.ts +8 -3
  18. package/dist/component/stateful.d.ts.map +1 -1
  19. package/dist/component/stateful.js +8 -8
  20. package/dist/component/stateful.js.map +1 -1
  21. package/dist/component/stateless.d.ts +0 -1
  22. package/dist/component/stateless.d.ts.map +1 -1
  23. package/dist/component/stateless.js +0 -6
  24. package/dist/component/stateless.js.map +1 -1
  25. package/dist/signal/computed.d.ts.map +1 -1
  26. package/dist/signal/computed.js +4 -2
  27. package/dist/signal/computed.js.map +1 -1
  28. package/dist/signal/effect.d.ts +6 -0
  29. package/dist/signal/effect.d.ts.map +1 -1
  30. package/dist/signal/effect.js +34 -0
  31. package/dist/signal/effect.js.map +1 -1
  32. package/dist/signal/signal.d.ts +4 -8
  33. package/dist/signal/signal.d.ts.map +1 -1
  34. package/dist/signal/signal.js +7 -3
  35. package/dist/signal/signal.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/__tests__/computed.test.ts +21 -0
  38. package/src/__tests__/effect.test.ts +92 -1
  39. package/src/__tests__/resource.test.ts +37 -0
  40. package/src/__tests__/signal.test.ts +19 -0
  41. package/src/async/resource.ts +7 -2
  42. package/src/component/base.ts +23 -3
  43. package/src/component/stateful.ts +8 -8
  44. package/src/component/stateless.ts +0 -6
  45. package/src/signal/computed.ts +5 -3
  46. package/src/signal/effect.ts +36 -0
  47. package/src/signal/signal.ts +15 -6
@@ -1 +1 @@
1
- {"version":3,"file":"stateful.js","sourceRoot":"","sources":["../../src/component/stateful.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,MAAM,OAAgB,iBAAkB,SAAQ,aAE/C;IAFD;;QAGE,4CAA4C;QACnC,cAAS,GAA4B,EAAE,CAAC;QAEjD,+FAA+F;QAC/F,gBAAW,GAAG,KAAK,CAAC;IAQtB,CAAC;IANC,SAAS,CAAC,KAA8B;QACtC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACxC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;CACF"}
1
+ {"version":3,"file":"stateful.js","sourceRoot":"","sources":["../../src/component/stateful.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,MAAM,OAAgB,iBAAkB,SAAQ,aAE/C;IAFD;;QAGE;;;WAGG;QACM,cAAS,GAA4B,EAAE,CAAC;QAEjD;;;WAGG;QACH,gBAAW,GAAG,KAAK,CAAC;IAEtB,CAAC;CAAA"}
@@ -4,7 +4,6 @@ type StatelessComponentType<T> = T & {
4
4
  children?: Children;
5
5
  };
6
6
  export declare abstract class StatelessComponent<T extends object = Record<never, never>> extends RootComponent<StatelessComponentType<T>> {
7
- _setProps(props: Record<string, unknown>): void;
8
7
  }
9
8
  export {};
10
9
  //# sourceMappingURL=stateless.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stateless.d.ts","sourceRoot":"","sources":["../../src/component/stateless.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,KAAK,sBAAsB,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE7D,8BAAsB,kBAAkB,CACtC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CACvC,SAAQ,aAAa,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAChD,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAMzC"}
1
+ {"version":3,"file":"stateless.d.ts","sourceRoot":"","sources":["../../src/component/stateless.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,KAAK,sBAAsB,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE7D,8BAAsB,kBAAkB,CACtC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CACvC,SAAQ,aAAa,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;CACjD"}
@@ -1,10 +1,4 @@
1
1
  import { RootComponent } from "./base";
2
2
  export class StatelessComponent extends RootComponent {
3
- _setProps(props) {
4
- Object.keys(this._rawProps).forEach((k) => {
5
- Reflect.deleteProperty(this._rawProps, k);
6
- });
7
- Object.assign(this._rawProps, props);
8
- }
9
3
  }
10
4
  //# sourceMappingURL=stateless.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stateless.js","sourceRoot":"","sources":["../../src/component/stateless.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAIvC,MAAM,OAAgB,kBAEpB,SAAQ,aAAwC;IAChD,SAAS,CAAC,KAA8B;QACtC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACxC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;CACF"}
1
+ {"version":3,"file":"stateless.js","sourceRoot":"","sources":["../../src/component/stateless.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAIvC,MAAM,OAAgB,kBAEpB,SAAQ,aAAwC;CACjD"}
@@ -1 +1 @@
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,CA4DxD;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"}
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"}
@@ -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
- runEffect(getRecompute());
45
+ const recomputeEffect = getRecompute();
46
+ cleanupEffectDeps(recomputeEffect);
47
+ runEffect(recomputeEffect);
46
48
  try {
47
49
  cachedValue = getter();
48
50
  dirty = false;
@@ -1 +1 @@
1
- {"version":3,"file":"computed.js","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"computed.js","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"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"}
@@ -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;AAEhC,eAAO,IAAI,YAAY,EAAE,MAAM,GAAG,IAAW,CAAC;AAG9C,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,QASnC;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,SAkBd,IAAI,CAK7B"}
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"}
@@ -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":"AAEA,MAAM,CAAC,IAAI,YAAY,GAAkB,IAAI,CAAC;AAC9C,MAAM,WAAW,GAAa,EAAE,CAAC;AAEjC,MAAM,UAAU,KAAK,CAAC,MAAc;IAClC,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,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,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,EAAE,EAAE,CAAC;QACZ,OAAO,GAAG,SAAS,CAAC;IACtB,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"effect.js","sourceRoot":"","sources":["../../src/signal/effect.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,8 @@
1
1
  import type { Signal } from "@praxisjs/shared";
2
- import { type Effect } from "./effect";
3
- export type SubList = Effect | Effect[] | null;
4
- export declare function addSub(holder: {
5
- subs: SubList;
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,EAAgB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAErD,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;AAE/C,wBAAgB,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CASnE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAStE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAoBhF;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CA4BpD"}
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"}
@@ -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 subs)
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 subs) {
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":"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"}
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.8.0",
3
+ "version": "1.8.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -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);
@@ -1,6 +1,14 @@
1
1
  import { describe, it, expect, vi } from "vitest";
2
2
 
3
- import { effect, track, activeEffect } from "../signal/effect";
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);
@@ -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
  };
@@ -1,17 +1,37 @@
1
1
  export abstract class RootComponent<T extends object = Record<string, never>> {
2
- /** Props passed by the parent — filled by the renderer when instantiating/updating. */
2
+ /**
3
+ * Props passed by the parent — filled by the renderer when instantiating/updating.
4
+ * @internal
5
+ */
3
6
  readonly _rawProps: T = {} as T;
4
7
 
5
- /** @internal — becomes `true` after `onMount` fires */
8
+ /**
9
+ * Becomes `true` after `onMount` fires.
10
+ * @internal
11
+ */
6
12
  _mounted = false;
7
13
 
8
- /** @internal — end comment anchor set by the runtime; used by decorators to locate the parent element */
14
+ /**
15
+ * End comment anchor set by the runtime; used by decorators to locate the parent element.
16
+ * @internal
17
+ */
9
18
  _anchor?: Comment;
10
19
 
11
20
  constructor(props: T = {} as T) {
12
21
  Object.assign(this._rawProps, props);
13
22
  }
14
23
 
24
+ /**
25
+ * Replaces the current props with a new set; called by the renderer on every parent re-render.
26
+ * @internal
27
+ */
28
+ _setProps(props: Record<string, unknown>) {
29
+ Object.keys(this._rawProps).forEach((k) => {
30
+ Reflect.deleteProperty(this._rawProps, k);
31
+ });
32
+ Object.assign(this._rawProps, props);
33
+ }
34
+
15
35
  get props(): T {
16
36
  return this._rawProps;
17
37
  }
@@ -3,16 +3,16 @@ import { RootComponent } from "./base";
3
3
  export abstract class StatefulComponent extends RootComponent<
4
4
  Record<string, unknown>
5
5
  > {
6
- /** Default values declared in the class. */
6
+ /**
7
+ * Default field values declared in the class, used to reset props on update.
8
+ * @internal
9
+ */
7
10
  readonly _defaults: Record<string, unknown> = {};
8
11
 
9
- /** @internal — set to true by @State on any write; cleared by renderer after each re-render */
12
+ /**
13
+ * Set to `true` by `@State` on any write; cleared by the renderer after each re-render.
14
+ * @internal
15
+ */
10
16
  _stateDirty = false;
11
17
 
12
- _setProps(props: Record<string, unknown>) {
13
- Object.keys(this._rawProps).forEach((k) => {
14
- Reflect.deleteProperty(this._rawProps, k);
15
- });
16
- Object.assign(this._rawProps, props);
17
- }
18
18
  }
@@ -7,10 +7,4 @@ type StatelessComponentType<T> = T & { children?: Children };
7
7
  export abstract class StatelessComponent<
8
8
  T extends object = Record<never, never>,
9
9
  > extends RootComponent<StatelessComponentType<T>> {
10
- _setProps(props: Record<string, unknown>) {
11
- Object.keys(this._rawProps).forEach((k) => {
12
- Reflect.deleteProperty(this._rawProps, k);
13
- });
14
- Object.assign(this._rawProps, props);
15
- }
16
10
  }
@@ -1,7 +1,7 @@
1
1
  import type { Computed, WritableComputed } from "@praxisjs/shared";
2
2
 
3
- import { activeEffect, runEffect, type Effect } from "./effect";
4
- import { addSub, removeSub, notifySubs, type SubList } from "./signal";
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
- runEffect(getRecompute());
50
+ const recomputeEffect = getRecompute();
51
+ cleanupEffectDeps(recomputeEffect);
52
+ runEffect(recomputeEffect);
51
53
  try {
52
54
  cachedValue = getter();
53
55
  dirty = false;