@praxisjs/core 2.2.0 → 2.3.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.
Files changed (116) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/component/internals.d.ts +12 -2
  3. package/dist/component/internals.d.ts.map +1 -1
  4. package/dist/component/internals.js +25 -7
  5. package/dist/component/internals.js.map +1 -1
  6. package/dist/internal.d.ts +3 -2
  7. package/dist/internal.d.ts.map +1 -1
  8. package/dist/internal.js +3 -2
  9. package/dist/internal.js.map +1 -1
  10. package/dist/signal/batch.d.ts +1 -3
  11. package/dist/signal/batch.d.ts.map +1 -1
  12. package/dist/signal/batch.js +4 -30
  13. package/dist/signal/batch.js.map +1 -1
  14. package/dist/signal/computed.d.ts.map +1 -1
  15. package/dist/signal/computed.js +13 -66
  16. package/dist/signal/computed.js.map +1 -1
  17. package/dist/signal/effect.d.ts +6 -14
  18. package/dist/signal/effect.d.ts.map +1 -1
  19. package/dist/signal/effect.js +11 -83
  20. package/dist/signal/effect.js.map +1 -1
  21. package/dist/signal/graph.d.ts +111 -0
  22. package/dist/signal/graph.d.ts.map +1 -0
  23. package/dist/signal/graph.js +513 -0
  24. package/dist/signal/graph.js.map +1 -0
  25. package/dist/signal/index.d.ts +1 -0
  26. package/dist/signal/index.d.ts.map +1 -1
  27. package/dist/signal/index.js +1 -0
  28. package/dist/signal/index.js.map +1 -1
  29. package/dist/signal/peek.d.ts.map +1 -1
  30. package/dist/signal/peek.js +2 -9
  31. package/dist/signal/peek.js.map +1 -1
  32. package/dist/signal/selector.d.ts +5 -0
  33. package/dist/signal/selector.d.ts.map +1 -0
  34. package/dist/signal/selector.js +128 -0
  35. package/dist/signal/selector.js.map +1 -0
  36. package/dist/signal/signal.d.ts +0 -5
  37. package/dist/signal/signal.d.ts.map +1 -1
  38. package/dist/signal/signal.js +11 -70
  39. package/dist/signal/signal.js.map +1 -1
  40. package/package.json +13 -4
  41. package/src/component/internals.ts +35 -10
  42. package/src/internal.ts +26 -0
  43. package/src/signal/batch.ts +5 -33
  44. package/src/signal/computed.ts +13 -66
  45. package/src/signal/effect.ts +12 -92
  46. package/src/signal/graph.ts +562 -0
  47. package/src/signal/index.ts +1 -0
  48. package/src/signal/peek.ts +2 -8
  49. package/src/signal/selector.ts +140 -0
  50. package/src/signal/signal.ts +11 -74
  51. package/dist/__tests__/batch.test.d.ts +0 -2
  52. package/dist/__tests__/batch.test.d.ts.map +0 -1
  53. package/dist/__tests__/batch.test.js +0 -124
  54. package/dist/__tests__/batch.test.js.map +0 -1
  55. package/dist/__tests__/component.test.d.ts +0 -2
  56. package/dist/__tests__/component.test.d.ts.map +0 -1
  57. package/dist/__tests__/component.test.js +0 -124
  58. package/dist/__tests__/component.test.js.map +0 -1
  59. package/dist/__tests__/computed.test.d.ts +0 -2
  60. package/dist/__tests__/computed.test.d.ts.map +0 -1
  61. package/dist/__tests__/computed.test.js +0 -289
  62. package/dist/__tests__/computed.test.js.map +0 -1
  63. package/dist/__tests__/effect.test.d.ts +0 -2
  64. package/dist/__tests__/effect.test.d.ts.map +0 -1
  65. package/dist/__tests__/effect.test.js +0 -200
  66. package/dist/__tests__/effect.test.js.map +0 -1
  67. package/dist/__tests__/peek.test.d.ts +0 -2
  68. package/dist/__tests__/peek.test.d.ts.map +0 -1
  69. package/dist/__tests__/peek.test.js +0 -26
  70. package/dist/__tests__/peek.test.js.map +0 -1
  71. package/dist/__tests__/persisted-signal.test.d.ts +0 -2
  72. package/dist/__tests__/persisted-signal.test.d.ts.map +0 -1
  73. package/dist/__tests__/persisted-signal.test.js +0 -252
  74. package/dist/__tests__/persisted-signal.test.js.map +0 -1
  75. package/dist/__tests__/reactive.test.d.ts +0 -2
  76. package/dist/__tests__/reactive.test.d.ts.map +0 -1
  77. package/dist/__tests__/reactive.test.js +0 -299
  78. package/dist/__tests__/reactive.test.js.map +0 -1
  79. package/dist/__tests__/resource-focus.test.d.ts +0 -2
  80. package/dist/__tests__/resource-focus.test.d.ts.map +0 -1
  81. package/dist/__tests__/resource-focus.test.js +0 -50
  82. package/dist/__tests__/resource-focus.test.js.map +0 -1
  83. package/dist/__tests__/resource-server-pass.test.d.ts +0 -2
  84. package/dist/__tests__/resource-server-pass.test.d.ts.map +0 -1
  85. package/dist/__tests__/resource-server-pass.test.js +0 -78
  86. package/dist/__tests__/resource-server-pass.test.js.map +0 -1
  87. package/dist/__tests__/resource.test.d.ts +0 -2
  88. package/dist/__tests__/resource.test.d.ts.map +0 -1
  89. package/dist/__tests__/resource.test.js +0 -336
  90. package/dist/__tests__/resource.test.js.map +0 -1
  91. package/dist/__tests__/signal.test.d.ts +0 -2
  92. package/dist/__tests__/signal.test.d.ts.map +0 -1
  93. package/dist/__tests__/signal.test.js +0 -254
  94. package/dist/__tests__/signal.test.js.map +0 -1
  95. package/dist/__tests__/synced.test.d.ts +0 -2
  96. package/dist/__tests__/synced.test.d.ts.map +0 -1
  97. package/dist/__tests__/synced.test.js +0 -142
  98. package/dist/__tests__/synced.test.js.map +0 -1
  99. package/dist/__tests__/track.test.d.ts +0 -2
  100. package/dist/__tests__/track.test.d.ts.map +0 -1
  101. package/dist/__tests__/track.test.js +0 -54
  102. package/dist/__tests__/track.test.js.map +0 -1
  103. package/src/__tests__/batch.test.ts +0 -138
  104. package/src/__tests__/component.test.ts +0 -165
  105. package/src/__tests__/computed.test.ts +0 -333
  106. package/src/__tests__/effect.test.ts +0 -241
  107. package/src/__tests__/peek.test.ts +0 -31
  108. package/src/__tests__/persisted-signal.test.ts +0 -305
  109. package/src/__tests__/reactive.test.ts +0 -343
  110. package/src/__tests__/resource-focus.test.ts +0 -61
  111. package/src/__tests__/resource-server-pass.test.ts +0 -90
  112. package/src/__tests__/resource.test.ts +0 -421
  113. package/src/__tests__/signal.test.ts +0 -291
  114. package/src/__tests__/synced.test.ts +0 -173
  115. package/src/__tests__/track.test.ts +0 -61
  116. package/tsconfig.json +0 -8
@@ -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,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAqD,eAAe,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAqB,YAAY,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAAU,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,EAAqD,eAAe,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAqB,YAAY,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAsC,QAAQ,EAAE,MAAM,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"peek.d.ts","sourceRoot":"","sources":["../../src/signal/peek.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAIzD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAQ1D"}
1
+ {"version":3,"file":"peek.d.ts","sourceRoot":"","sources":["../../src/signal/peek.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAIzD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAE1D"}
@@ -1,12 +1,5 @@
1
- import { activeEffect, runEffect } from "./effect";
1
+ import { untrack } from "./effect";
2
2
  export function peek(source) {
3
- const prev = activeEffect;
4
- runEffect(null);
5
- try {
6
- return source();
7
- }
8
- finally {
9
- runEffect(prev);
10
- }
3
+ return untrack(source);
11
4
  }
12
5
  //# sourceMappingURL=peek.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"peek.js","sourceRoot":"","sources":["../../src/signal/peek.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,UAAU,IAAI,CAAI,MAA+B;IACrD,MAAM,IAAI,GAAG,YAAY,CAAC;IAC1B,SAAS,CAAC,IAAI,CAAC,CAAC;IAChB,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"peek.js","sourceRoot":"","sources":["../../src/signal/peek.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,MAAM,UAAU,IAAI,CAAI,MAA+B;IACrD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export type Selector<K> = (key: K) => boolean;
2
+ export type SelectorEquals<K, T> = (key: K, value: T) => boolean;
3
+ export declare function selector<T>(source: () => T): Selector<T>;
4
+ export declare function selector<T, K>(source: () => T, equals: SelectorEquals<K, T>): Selector<K>;
5
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../src/signal/selector.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;AAC9C,MAAM,MAAM,cAAc,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;AAyCjE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC1D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,128 @@
1
+ import { activeSub, createEffect, DIRTY, disposeEffect, endBatch, notify, PENDING, startBatch, track, WatchedNode } from "./graph";
2
+ // Keys live in a Map, so the default comparison has to be the Map's own (SameValueZero): a key
3
+ // that compares equal must be the one the lookup finds, or a bucket would never be notified.
4
+ function sameValueZero(a, b) {
5
+ return a === b || (Number.isNaN(a) && Number.isNaN(b));
6
+ }
7
+ // One class for every selector's buckets (rather than one declared per selector call), so the
8
+ // graph code that reads and links them always sees the same shape.
9
+ // Declared and assigned rather than parameter properties, which compile to class fields that are
10
+ // defined one by one and make buckets (one per row reading a selector) slower to create.
11
+ class Bucket extends WatchedNode {
12
+ constructor(key, owner) {
13
+ super();
14
+ this.key = key;
15
+ this.owner = owner;
16
+ }
17
+ // A bucket drops itself from its selector once its last subscriber unlinks (re-run or disposal).
18
+ unwatched() {
19
+ this.owner.drop(this.key);
20
+ }
21
+ }
22
+ // Keeps the bucket shape alive across full GCs; see the shape keepers in graph.ts.
23
+ Object.defineProperty(Bucket, "shapeKeeper", {
24
+ value: new Bucket(undefined, {
25
+ drop() {
26
+ // Never called: the keeper has no subscriber to lose.
27
+ },
28
+ }),
29
+ });
30
+ export function selector(source, equals) {
31
+ const compare = equals ?? sameValueZero;
32
+ const buckets = new Map();
33
+ let current;
34
+ let sourceEffect = null;
35
+ let releaseScheduled = false;
36
+ // The source subscription goes with the last bucket, so removed rows leave nothing behind.
37
+ const owner = {
38
+ drop(key) {
39
+ buckets.delete(key);
40
+ if (buckets.size === 0)
41
+ scheduleRelease();
42
+ },
43
+ };
44
+ function changedBuckets(prev, next) {
45
+ const changed = [];
46
+ if (equals === undefined) {
47
+ if (sameValueZero(prev, next))
48
+ return changed;
49
+ const prevBucket = buckets.get(prev);
50
+ const nextBucket = buckets.get(next);
51
+ if (prevBucket !== undefined)
52
+ changed.push(prevBucket);
53
+ if (nextBucket !== undefined)
54
+ changed.push(nextBucket);
55
+ return changed;
56
+ }
57
+ for (const [key, bucket] of buckets) {
58
+ if (compare(key, prev) !== compare(key, next))
59
+ changed.push(bucket);
60
+ }
61
+ return changed;
62
+ }
63
+ // Batched so an effect reading several affected keys re-runs once, and so no subscriber runs
64
+ // (and unlinks) while the buckets are still being walked.
65
+ function notifyChanged(prev, next) {
66
+ const changed = changedBuckets(prev, next);
67
+ if (changed.length === 0)
68
+ return;
69
+ startBatch();
70
+ try {
71
+ for (const bucket of changed) {
72
+ bucket.version++;
73
+ notify(bucket);
74
+ }
75
+ }
76
+ finally {
77
+ endBatch();
78
+ }
79
+ }
80
+ function activate() {
81
+ let initialized = false;
82
+ sourceEffect = createEffect(() => {
83
+ const next = source();
84
+ const prev = current;
85
+ current = next;
86
+ if (!initialized) {
87
+ initialized = true;
88
+ return;
89
+ }
90
+ notifyChanged(prev, next);
91
+ });
92
+ }
93
+ // An effect re-running unlinks its bucket right before reading the key again, and a list
94
+ // re-render unlinks every bucket before the new rows subscribe. Waiting a microtask keeps the
95
+ // source subscribed through both instead of stopping and re-reading it each time.
96
+ function scheduleRelease() {
97
+ if (releaseScheduled)
98
+ return;
99
+ releaseScheduled = true;
100
+ queueMicrotask(() => {
101
+ releaseScheduled = false;
102
+ if (buckets.size > 0)
103
+ return;
104
+ const stop = sourceEffect;
105
+ sourceEffect = null;
106
+ disposeEffect(stop);
107
+ });
108
+ }
109
+ return function select(key) {
110
+ if (activeSub === undefined) {
111
+ // `current` lags the source while the source effect waits to run (a write inside a batch, or
112
+ // a computed source, whose effects run a microtask later). A tracked read keeps the cached
113
+ // value, which the effect's run then corrects through the buckets; nothing corrects this one.
114
+ const fresh = sourceEffect !== null && (sourceEffect.flags & (DIRTY | PENDING)) === 0;
115
+ return compare(key, fresh ? current : source());
116
+ }
117
+ if (sourceEffect === null)
118
+ activate();
119
+ let bucket = buckets.get(key);
120
+ if (bucket === undefined) {
121
+ bucket = new Bucket(key, owner);
122
+ buckets.set(key, bucket);
123
+ }
124
+ track(bucket);
125
+ return compare(key, current);
126
+ };
127
+ }
128
+ //# sourceMappingURL=selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../../src/signal/selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAmB,MAAM,SAAS,CAAC;AAKpJ,+FAA+F;AAC/F,6FAA6F;AAC7F,SAAS,aAAa,CAAC,CAAU,EAAE,CAAU;IAC3C,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAMD,8FAA8F;AAC9F,mEAAmE;AACnE,iGAAiG;AACjG,yFAAyF;AACzF,MAAM,MAAU,SAAQ,WAAW;IAIjC,YAAY,GAAM,EAAE,KAAqB;QACvC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,iGAAiG;IACjG,SAAS;QACP,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;CACF;AAED,mFAAmF;AACnF,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE;IAC3C,KAAK,EAAE,IAAI,MAAM,CAAU,SAAS,EAAE;QACpC,IAAI;YACF,sDAAsD;QACxD,CAAC;KACF,CAAC;CACH,CAAC,CAAC;AAIH,MAAM,UAAU,QAAQ,CAAO,MAAe,EAAE,MAA6B;IAC3E,MAAM,OAAO,GAAG,MAAM,IAAK,aAAsC,CAAC;IAClE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,IAAI,OAAU,CAAC;IACf,IAAI,YAAY,GAAsB,IAAI,CAAC;IAC3C,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAE7B,2FAA2F;IAC3F,MAAM,KAAK,GAAmB;QAC5B,IAAI,CAAC,GAAG;YACN,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpB,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;gBAAE,eAAe,EAAE,CAAC;QAC5C,CAAC;KACF,CAAC;IAEF,SAAS,cAAc,CAAC,IAAO,EAAE,IAAO;QACtC,MAAM,OAAO,GAAkB,EAAE,CAAC;QAClC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC;gBAAE,OAAO,OAAO,CAAC;YAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,IAAoB,CAAC,CAAC;YACrD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,IAAoB,CAAC,CAAC;YACrD,IAAI,UAAU,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACvD,IAAI,UAAU,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACvD,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACpC,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,6FAA6F;IAC7F,0DAA0D;IAC1D,SAAS,aAAa,CAAC,IAAO,EAAE,IAAO;QACrC,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACjC,UAAU,EAAE,CAAC;QACb,IAAI,CAAC;YACH,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,CAAC,OAAO,EAAE,CAAC;gBACjB,MAAM,CAAC,MAAM,CAAC,CAAC;YACjB,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,QAAQ,EAAE,CAAC;QACb,CAAC;IACH,CAAC;IAED,SAAS,QAAQ;QACf,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,YAAY,GAAG,YAAY,CAAC,GAAG,EAAE;YAC/B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,OAAO,CAAC;YACrB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,WAAW,GAAG,IAAI,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,yFAAyF;IACzF,8FAA8F;IAC9F,kFAAkF;IAClF,SAAS,eAAe;QACtB,IAAI,gBAAgB;YAAE,OAAO;QAC7B,gBAAgB,GAAG,IAAI,CAAC;QACxB,cAAc,CAAC,GAAG,EAAE;YAClB,gBAAgB,GAAG,KAAK,CAAC;YACzB,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC;gBAAE,OAAO;YAC7B,MAAM,IAAI,GAAG,YAA0B,CAAC;YACxC,YAAY,GAAG,IAAI,CAAC;YACpB,aAAa,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,SAAS,MAAM,CAAC,GAAM;QAC3B,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,6FAA6F;YAC7F,2FAA2F;YAC3F,8FAA8F;YAC9F,MAAM,KAAK,GAAG,YAAY,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,KAAK,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;YACtF,OAAO,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QAClD,CAAC;QACD,IAAI,YAAY,KAAK,IAAI;YAAE,QAAQ,EAAE,CAAC;QACtC,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,GAAG,IAAI,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAChC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC3B,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,OAAO,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC/B,CAAC,CAAC;AACJ,CAAC"}
@@ -1,8 +1,3 @@
1
1
  import type { Signal } from "@praxisjs/shared";
2
- import { removeSub, 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 { removeSub };
6
- export declare function notifySubs(subs: Exclude<SubList, null>, batching: boolean): void;
7
2
  export declare function signal<T>(initialValue: T): Signal<T>;
8
3
  //# 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,EAGL,SAAS,EAET,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,CAalE;AAED,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,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
+ {"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAK/C,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAepD"}
@@ -1,75 +1,16 @@
1
- import { isBatching, enqueueEffect } from "./batch";
2
- import { activeEffect, recordDependency, removeSub, SUBS_SET_THRESHOLD, } from "./effect";
3
- export function addSub(holder, eff) {
4
- const subs = holder.subs;
5
- if (subs === null) {
6
- holder.subs = eff;
7
- }
8
- else if (typeof subs === "function") {
9
- if (subs !== eff)
10
- holder.subs = [subs, eff];
11
- }
12
- else if (subs instanceof Set) {
13
- subs.add(eff);
14
- }
15
- else if (!subs.includes(eff)) {
16
- subs.push(eff);
17
- if (subs.length > SUBS_SET_THRESHOLD)
18
- holder.subs = new Set(subs);
19
- }
20
- recordDependency(eff, holder);
21
- }
22
- export { removeSub };
23
- export function notifySubs(subs, batching) {
24
- if (typeof subs === "function") {
25
- if (batching)
26
- enqueueEffect(subs);
27
- else
28
- subs();
29
- return;
30
- }
31
- const snapshot = [...subs];
32
- if (batching) {
33
- for (const sub of snapshot)
34
- enqueueEffect(sub);
35
- return;
36
- }
37
- let lastError;
38
- let hasError = false;
39
- for (const sub of snapshot) {
40
- try {
41
- sub();
42
- }
43
- catch (e) {
44
- lastError = e;
45
- hasError = true;
46
- }
47
- }
48
- if (hasError)
49
- throw lastError;
50
- }
1
+ import { untrack } from "./effect";
2
+ import { createEffect, disposeEffect, readSignal, SignalNode, writeSignal } from "./graph";
51
3
  export function signal(initialValue) {
52
- let value = initialValue;
53
- const holder = { subs: null };
54
- function read() {
55
- if (activeEffect !== null)
56
- addSub(holder, activeEffect);
57
- return value;
58
- }
59
- const s = read;
60
- s.set = (newValue) => {
61
- if (Object.is(value, newValue))
62
- return;
63
- value = newValue;
64
- if (holder.subs !== null)
65
- notifySubs(holder.subs, isBatching());
66
- };
67
- s.update = (fn) => { s.set(fn(value)); };
4
+ const node = new SignalNode(initialValue);
5
+ const s = (() => readSignal(node));
6
+ s.set = (newValue) => { writeSignal(node, newValue); };
7
+ s.update = (fn) => { writeSignal(node, fn(node.value)); };
68
8
  s.subscribe = (fn) => {
69
- const wrapped = () => { fn(value); };
70
- addSub(holder, wrapped);
71
- wrapped();
72
- return () => { removeSub(holder, wrapped); };
9
+ const sub = createEffect(() => {
10
+ const value = readSignal(node);
11
+ untrack(() => { fn(value); });
12
+ });
13
+ return () => { disposeEffect(sub); };
73
14
  };
74
15
  s.__isSignal = true;
75
16
  return s;
@@ -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":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3F,MAAM,UAAU,MAAM,CAAI,YAAe;IACvC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;IAE1C,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAc,CAAC;IAChD,CAAC,CAAC,GAAG,GAAG,CAAC,QAAW,EAAE,EAAE,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,MAAM,GAAG,CAAC,EAAkB,EAAE,EAAE,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,CAAC,CAAC,SAAS,GAAG,CAAC,EAAsB,EAAE,EAAE;QACvC,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,EAAE;YAC5B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC/B,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC;IACF,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,CAAC;AACX,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@praxisjs/core",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
+ "sideEffects": false,
5
6
  "main": "./dist/index.js",
6
7
  "types": "./dist/index.d.ts",
7
8
  "exports": {
@@ -14,14 +15,22 @@
14
15
  "types": "./dist/internal.d.ts"
15
16
  }
16
17
  },
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "!dist/**/__tests__",
22
+ "!src/**/__tests__",
23
+ "CHANGELOG.md"
24
+ ],
17
25
  "devDependencies": {
18
26
  "typescript": "^6.0.3"
19
27
  },
20
28
  "dependencies": {
21
- "@praxisjs/shared": "0.3.1"
29
+ "@praxisjs/shared": "0.3.2"
22
30
  },
23
31
  "scripts": {
24
- "build": "tsc",
25
- "dev": "tsc --watch"
32
+ "build": "tsc -p tsconfig.build.json",
33
+ "dev": "tsc -p tsconfig.build.json --watch",
34
+ "typecheck": "tsc --noEmit"
26
35
  }
27
36
  }
@@ -3,15 +3,22 @@ export const componentPropsType: unique symbol = Symbol("praxis.componentPropsTy
3
3
  interface ComponentInternals<T extends object> {
4
4
  rawProps: T;
5
5
  mounted: boolean;
6
- anchor?: Comment;
6
+ anchor: Node | undefined;
7
+ start: Comment | undefined;
7
8
  defaults: Record<string, unknown>;
8
9
  stateDirty: boolean;
9
10
  }
10
11
 
11
- const componentInternals = new WeakMap<object, ComponentInternals<object>>();
12
+ // Kept on the instance under a symbol rather than in a WeakMap: every @Prop read goes through
13
+ // here, and a property load is much cheaper than a WeakMap lookup.
14
+ const INTERNALS: unique symbol = Symbol("praxis.internals");
15
+
16
+ interface InternalsHost {
17
+ [INTERNALS]?: ComponentInternals<object>;
18
+ }
12
19
 
13
20
  function getInternals(instance: object): ComponentInternals<object> {
14
- const internals = componentInternals.get(instance);
21
+ const internals = (instance as InternalsHost)[INTERNALS];
15
22
  if (!internals) {
16
23
  throw new TypeError("Expected a PraxisJS component instance.");
17
24
  }
@@ -22,12 +29,14 @@ export function initComponentInternals(
22
29
  instance: object,
23
30
  props: object,
24
31
  ): void {
25
- componentInternals.set(instance, {
32
+ (instance as InternalsHost)[INTERNALS] = {
26
33
  rawProps: { ...props },
27
34
  mounted: false,
35
+ anchor: undefined,
36
+ start: undefined,
28
37
  defaults: {},
29
38
  stateDirty: false,
30
- });
39
+ };
31
40
  }
32
41
 
33
42
  export function getComponentProps(instance: object): object {
@@ -73,13 +82,17 @@ export function getComponentDefaults(
73
82
  return getInternals(instance).defaults;
74
83
  }
75
84
 
76
- export function getComponentAnchor(instance: object): Comment | undefined {
85
+ /**
86
+ * A node of the mounted component's output: its only root node, or the comment that closes its
87
+ * region when it renders several nodes. Its `parentElement` is the element the component sits in.
88
+ */
89
+ export function getComponentAnchor(instance: object): Node | undefined {
77
90
  return getInternals(instance).anchor;
78
91
  }
79
92
 
80
93
  export function setComponentAnchor(
81
94
  instance: object,
82
- anchor: Comment | undefined,
95
+ anchor: Node | undefined,
83
96
  ): void {
84
97
  getInternals(instance).anchor = anchor;
85
98
  }
@@ -96,14 +109,14 @@ export function setComponentMounted(
96
109
  }
97
110
 
98
111
  export function markStateDirty(instance: object): void {
99
- const internals = componentInternals.get(instance);
112
+ const internals = (instance as InternalsHost)[INTERNALS];
100
113
  if (internals) {
101
114
  internals.stateDirty = true;
102
115
  }
103
116
  }
104
117
 
105
118
  export function isStateDirty(instance: object): boolean {
106
- const internals = componentInternals.get(instance);
119
+ const internals = (instance as InternalsHost)[INTERNALS];
107
120
  if (internals) return internals.stateDirty;
108
121
  return false;
109
122
  }
@@ -112,8 +125,20 @@ export function setStateDirty(
112
125
  instance: object,
113
126
  stateDirty: boolean,
114
127
  ): void {
115
- const internals = componentInternals.get(instance);
128
+ const internals = (instance as InternalsHost)[INTERNALS];
116
129
  if (internals) {
117
130
  internals.stateDirty = stateDirty;
118
131
  }
119
132
  }
133
+
134
+ /**
135
+ * The comment that opens a component's region, when it has one: a component whose render sets it
136
+ * (with the closing comment as its anchor) brings its own boundaries, and mountComponent adds none.
137
+ */
138
+ export function getComponentStart(instance: object): Comment | undefined {
139
+ return getInternals(instance).start;
140
+ }
141
+
142
+ export function setComponentStart(instance: object, start: Comment | undefined): void {
143
+ getInternals(instance).start = start;
144
+ }
package/src/internal.ts CHANGED
@@ -15,11 +15,35 @@ export {
15
15
  batch,
16
16
  effect,
17
17
  untrack,
18
+ selector,
19
+ type Selector,
20
+ type SelectorEquals,
18
21
  type PersistedSignalOptions,
19
22
  type PersistedSignal,
20
23
  type SyncedSignal,
21
24
  } from "./signal";
22
25
  export { RootComponent } from "./component";
26
+ export {
27
+ createEffect,
28
+ createContextEffect,
29
+ disposeEffect,
30
+ startBatch,
31
+ endBatch,
32
+ EffectNode,
33
+ SignalNode,
34
+ readSignal,
35
+ writeSignal,
36
+ ComputedNode,
37
+ readComputed,
38
+ type ReactiveNode,
39
+ pauseTracking,
40
+ resumeTracking,
41
+ setReactiveObserver,
42
+ setCurrentOwner,
43
+ getEffectOwner,
44
+ type ReactiveObserver,
45
+ type EffectCleanup,
46
+ } from "./signal/graph";
23
47
  export {
24
48
  componentPropsType,
25
49
  getComponentAnchor,
@@ -27,6 +51,8 @@ export {
27
51
  getComponentDefaults,
28
52
  getComponentProps,
29
53
  getComponentRawProp,
54
+ getComponentStart,
55
+ setComponentStart,
30
56
  isComponentMounted,
31
57
  isStateDirty,
32
58
  markStateDirty,
@@ -1,39 +1,11 @@
1
- import type { Effect } from "./effect";
1
+ import { endBatch, startBatch } from "./graph";
2
2
 
3
- const batchEffects = new Set<Effect>();
4
- let batchDepth = 0;
5
-
6
- export function isBatching(): boolean {
7
- return batchDepth > 0;
8
- }
9
-
10
- export function enqueueEffect(effect: Effect): void {
11
- batchEffects.add(effect);
12
- }
13
-
14
- export function batch(fn: () => void) {
15
- batchDepth++;
3
+ /** Runs `fn` and defers every effect it invalidates until it returns, so each runs once. */
4
+ export function batch(fn: () => void): void {
5
+ startBatch();
16
6
  try {
17
7
  fn();
18
8
  } finally {
19
- if (--batchDepth === 0) flush();
20
- }
21
- }
22
-
23
- // The queue is detached before running so a throwing effect can't leave stale entries behind,
24
- // and every queued effect still runs — matching how notifySubs treats errors outside a batch.
25
- function flush(): void {
26
- const queued = [...batchEffects];
27
- batchEffects.clear();
28
- let lastError: unknown;
29
- let hasError = false;
30
- for (const effect of queued) {
31
- try {
32
- effect();
33
- } catch (e) {
34
- lastError = e;
35
- hasError = true;
36
- }
9
+ endBatch();
37
10
  }
38
- if (hasError) throw lastError;
39
11
  }
@@ -1,82 +1,29 @@
1
1
  import type { Computed, WritableComputed } from "@praxisjs/shared";
2
2
 
3
- import { activeEffect, cleanupEffectDeps, runEffect, type Effect, type SubList } from "./effect";
4
- import { addSub, removeSub, notifySubs } from "./signal";
5
-
6
- type ComputedRecompute = Effect & { __isComputedRecompute: true };
7
-
8
- function isComputedRecompute(eff: Effect): boolean {
9
- return (eff as Partial<ComputedRecompute>).__isComputedRecompute === true;
10
- }
3
+ import { untrack } from "./effect";
4
+ import { ComputedNode, createEffect, DIRTY, disposeEffect, readComputed, unlinkDeps } from "./graph";
11
5
 
12
6
  export function computed<T>(getter: () => T): Computed<T> {
13
- let cachedValue: T | undefined;
14
- let dirty = true;
15
-
16
- const computedHolder = { subs: null as SubList };
17
- const leafHolder = { subs: null as SubList };
18
- let scheduled = false;
19
-
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;
26
- dirty = true;
27
- if (computedHolder.subs !== null) notifySubs(computedHolder.subs, false);
28
- if (leafHolder.subs !== null && !scheduled) {
29
- scheduled = true;
30
- queueMicrotask(() => {
31
- scheduled = false;
32
- if (leafHolder.subs !== null) notifySubs(leafHolder.subs, false);
33
- });
34
- }
35
- } as ComputedRecompute;
36
- recompute.__isComputedRecompute = true;
37
- return recompute;
38
- }
39
-
40
- function read(): T {
41
- if (activeEffect !== null) {
42
- if (isComputedRecompute(activeEffect)) {
43
- addSub(computedHolder, activeEffect);
44
- } else {
45
- addSub(leafHolder, activeEffect);
46
- }
47
- }
48
- if (dirty) {
49
- const prevEffect = activeEffect;
50
- const recomputeEffect = getRecompute();
51
- cleanupEffectDeps(recomputeEffect);
52
- runEffect(recomputeEffect);
53
- try {
54
- cachedValue = getter();
55
- dirty = false;
56
- } finally {
57
- runEffect(prevEffect);
58
- }
59
- }
60
- return cachedValue as T;
61
- }
7
+ const node = new ComputedNode(getter);
62
8
 
63
- const c = read as Computed<T> & { dispose(): void };
9
+ const c = (() => readComputed(node)) as Computed<T> & { dispose(): void };
64
10
 
65
11
  // Drops this computed's subscriptions to its sources so an unreachable one (e.g. an evicted
66
12
  // @Memo entry) isn't kept alive by the signals it read. Does nothing while anything still
67
13
  // depends on it: detaching then would leave those dependents silently stale.
68
14
  c.dispose = () => {
69
- if (leafHolder.subs !== null || computedHolder.subs !== null) return;
70
- if (recompute !== null) cleanupEffectDeps(recompute);
71
- dirty = true;
72
- cachedValue = undefined;
15
+ if (node.subs !== undefined) return;
16
+ unlinkDeps(node);
17
+ node.flags |= DIRTY;
18
+ node.value = undefined;
73
19
  };
74
20
 
75
21
  c.subscribe = (fn: (value: T) => void) => {
76
- const wrapped: Effect = () => { fn(read()); };
77
- addSub(leafHolder, wrapped);
78
- wrapped();
79
- return () => { removeSub(leafHolder, wrapped); };
22
+ const sub = createEffect(() => {
23
+ const value = readComputed(node);
24
+ untrack(() => { fn(value); });
25
+ });
26
+ return () => { disposeEffect(sub); };
80
27
  };
81
28
 
82
29
  c.__isComputed = true;