@webreflection/signals 0.1.7 → 0.1.8

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/README.md CHANGED
@@ -92,3 +92,106 @@ I mean ... that's coding, isn't it ... today I really needed something that woul
92
92
  There is a *huge* difference between *NodeJS* and *Bun* but that's likely because *JSC* handles *Set* or *Map* in a better way, meaning all *WebKit* based browsers and mobile devices will have similar *Preact* performance, while *Chromium* based browsers will have half Preact size, but 1.5X slowdown.
93
93
 
94
94
  However, in common scenarios with no more than 10 to 100 signals per *effect*, the performance are consistently better or really close to Preact.
95
+
96
+ ## Architecture
97
+
98
+ Fine-tuned signals are a piece of art:
99
+
100
+ * fastest possible feedback
101
+ * linked graphs with bitwise flags deciding what to do and/or when (alien-signals)
102
+ * transpiled and understood ahead of time to produce the best possible outcome (Svelte, SolidJS)
103
+ * ad-hoc or coupled DOM manipulation
104
+ * specific to JSX syntax (Preact or alternatives)
105
+ * ... other attempts/variants out there
106
+
107
+ That is all good and fine, yet the graph behind *signals* is, *imho*, pretty simple in theory (clearly hard in practice) ... and I will tell you how this module keeps that *simple* concept in mind.
108
+
109
+ ### Signals
110
+
111
+ These are just a *value wrapper*: you reach that *value*? You are subscribing to it. You change that *value*? You are triggering anything listening to that *signal* reference after subscribing.
112
+
113
+ That's it, that's the contract!
114
+
115
+ Here, there is a `.peek()` method to avoid subscribing, but any time you access a `signal.value`, you are subscribing to it if you are either a *computed* reference or an *event* one.
116
+
117
+ ### Computed
118
+
119
+ It's a `signal` by all means, because once you reach its `value`, it subscribes to any *subscriber*, just like any *signal* would do.
120
+
121
+ The main difference between *computed* and *signal* is that *computed* is a **read-only** contract, and it expects a callback as an argument that will "*brand*" that computed *type* from then on.
122
+
123
+ Everything else is the same: you cannot `computed.value = anything` but you can always retrieve `computed.value` to subscribe to that computed.
124
+
125
+ ### Effect
126
+
127
+ This is the whole orchestration around *signals* or *computed* that makes anything **reactive**, but because it's a *bottom-up* situation we're dealing with, things might feel overly complicated. In theory, that's not the case.
128
+
129
+ ```js
130
+ const num = signal(0);
131
+
132
+ const dispose = effect(() => {
133
+ // subscribe to this signal state
134
+ const value = num.value;
135
+
136
+ // make this effect able to dispose itself
137
+ if (2 <= value) {
138
+ // no further changes to num will be observed
139
+ dispose();
140
+ }
141
+ else {
142
+ console.log({ value });
143
+ }
144
+ });
145
+
146
+ // logs: { value: 0 }
147
+
148
+ // increment by 1
149
+ num.value++;
150
+ // logs: { value: 1 }
151
+
152
+ // increment by 1
153
+ num.value++;
154
+ // logs nothing!
155
+
156
+ // drop reactivity explicitly!
157
+ dispose();
158
+
159
+ // increment by 1
160
+ num.value++;
161
+ // also logs nothing!
162
+ ```
163
+
164
+ In this example, the *effect* subscribes to `num` changes, but it seppukus itself once its `value` reaches the number `2` or above ("*how is that possible?*" ... you'll learn that in a bit!).
165
+
166
+ The architecture in this example is also easy to explain: any *signal* or *computed* value that is reached will consider its outer *effect* a potential subscriber!
167
+
168
+ The important difference in this module is that *effect* has no notion of what it subscribed to. It's the *signal* or *computed* that retains that information, not the consumer, and that's simply because invoking any foreign *callback* doesn't mean you know what happens within that *callback*. Not knowing what happens is indeed a great way to understand this architecture.
169
+
170
+ Long story short, any *callback* executing within an *effect* is registered as a consumer of the current *signal* or *computed* reference that is required to provide a `.value` while the *callback* is happening, so the relation is from *signal* or *computed* to any running *callback*, if **any**.
171
+
172
+
173
+ #### Effect in details
174
+
175
+ No *effect*? No reactivity! This is the *signals* contract, but there is a *catch*:
176
+
177
+ * what if an *effect* is within another *effect*?
178
+ * how can **conditional** operators ditch what *sub-effect* should run and what shouldn't?
179
+
180
+ Great questions. Here are the details about why that's never a concern:
181
+
182
+ * *effect* never subscribes to changes, it just registers itself as an *observer*
183
+ * *effect* never runs if it knows outer *effects* are queued to resolve the latest change
184
+ * the previous point means if `signal.value` is registered both at the *inner* effect level and at the *outer* one, the *outer* one will dictate the execution because ...
185
+ * only the top-most subscribed effects will eventually execute, and ...
186
+ * any *effect* previously registered for its outer *effect* will be **disposed** and never react to anything again!
187
+
188
+ I am not sure you are still following, but because *effect* is a bottom-up problem, top-down is clearly the solution, and that's granted by the registration **stack**, where the outer *effect* runs before the *inner effect*. That solves everything!
189
+
190
+
191
+ ### Batch
192
+
193
+ If you followed everything else I've explained around this architecture, `batch(callback)` simply represents a running *callback* with no tracking whatsoever, except the implementation keeps tracing what should run fresh and what doesn't exist anymore. The latest *effect* that got it right will trigger and eventually access, or *re-subscribe* to, anything in it, but just once, after the *batch* callback has finished.
194
+
195
+ ### Untracked
196
+
197
+ This utility basically runs updates and whatnot like *batch*, but it will never register itself while doing that execution, resulting in a safe hook for foreign functions that wouldn't otherwise belong to our logic. They are just interested in our data instead, and that's fine!
package/dist/branded.js CHANGED
@@ -1 +1 @@
1
- var c=!0,l=t=>{c=t},n,i=t=>{c&&n&&t.add(n)},a=(t,e)=>{let r=n;n=t;try{return e()}finally{n=r}};var u=new WeakSet,p=new WeakMap,f,W=t=>{let e=f;e||(f=[]);try{return t()}finally{if(!e){[e,f]=[f,e];for(let[r,o]of e)u.has(r)||o()}}},b=t=>{let e=p.get(t);if(e.length)for(let o of e.splice(0))x(o)},x=t=>{u.add(t),b(t),p.delete(t)},g=t=>{let e=()=>{o||u.has(e)||(o=!0,n||(f?f.push([e,r]):r()))},r=()=>{for(;o;)if(o=!1,b(e),s?.(),s=a(e,t),u.has(e))return},o=!0,s;return n&&p.get(n).push(e),p.set(e,[]),r(),()=>{p.has(e)&&(s?.(),x(e))}};var h=t=>{let e=new Set,r=!0,o,s=()=>{if(r)return;r=!0;let w=e;e=new Set;for(let y of w)y()},d=()=>{for(;r;)r=!1,o=a(s,t);return o};return{get value(){return i(e),d()},peek(){return d()}}};var k=t=>{let e=new Set;return{get value(){return i(e),t},set value(r){if(t=r,c){let o=e;e=new Set;for(let s of o)s()}},peek(){return t}}};var z=t=>{let e=c;l(!1);try{return t()}finally{l(e)}};var H=t=>function(...r){let o,s=g(()=>{o??=t.apply(this,r)??this});return o[Symbol.dispose]=s,o};var m=new WeakSet,N=t=>{let e=h(t);return m.add(e),e},O=t=>m.has(t),P=t=>{let e=k(t);return m.add(e),e};export{W as batch,N as computed,H as disposable,g as effect,O as isSignal,P as signal,z as untracked};
1
+ var c=!0,l=t=>{c=t},n,a=t=>{c&&n&&t.add(n)},i=(t,e)=>{let r=n;n=t;try{return e()}finally{n=r}};var u=new WeakSet,p=new WeakMap,f,W=t=>{let e=f;e||(f=[]);try{return t()}finally{if(!e){[e,f]=[f,e];for(let[r,o]of e)u.has(r)||o()}}},d=t=>{let e=p.get(t);if(e.length)for(let o of e.splice(0))b(o)},b=t=>{u.add(t),d(t),p.delete(t)},x=t=>{let e=()=>{o||u.has(e)||(o=!0,n||(f?f.push([e,r]):r()))},r=()=>{for(;o;)if(o=!1,d(e),s?.(),s=i(e,t),u.has(e))return},o=!0,s;return n&&p.get(n).push(e),p.set(e,[]),r(),()=>{p.has(e)&&(s?.(),b(e))}};var h=t=>{let e=new Set,r=!0,o,s=()=>{for(;r;)r=!1,o=i(k,t);return o},k=()=>{if(r)return;r=!0;let w=e;e=new Set;for(let y of w)y()};return{get value(){return a(e),s()},peek:s}};var g=t=>{let e=new Set;return{get value(){return a(e),t},set value(r){if(t=r,c){let o=e;e=new Set;for(let s of o)s()}},peek(){return t}}};var z=t=>{let e=c;l(!1);try{return t()}finally{l(e)}};var H=t=>function(...r){let o,s=x(()=>{o??=t.apply(this,r)??this});return o[Symbol.dispose]=s,o};var m=new WeakSet,N=t=>{let e=h(t);return m.add(e),e},O=t=>m.has(t),P=t=>{let e=g(t);return m.add(e),e};export{W as batch,N as computed,H as disposable,x as effect,O as isSignal,P as signal,z as untracked};
@@ -1 +1 @@
1
- var c=!0,a=t=>{c=t},n,i=t=>{c&&n&&t.add(n)},p=(t,e)=>{let r=n;n=t;try{return e()}finally{n=r}};var l=new WeakSet,u=new WeakMap,f,y=t=>{let e=f;e||(f=[]);try{return t()}finally{if(!e){[e,f]=[f,e];for(let[r,o]of e)l.has(r)||o()}}},h=t=>{let e=u.get(t);if(e.length)for(let o of e.splice(0))m(o)},m=t=>{l.add(t),h(t),u.delete(t)},d=t=>{let e=()=>{o||l.has(e)||(o=!0,n||(f?f.push([e,r]):r()))},r=()=>{for(;o;)if(o=!1,h(e),s?.(),s=p(e,t),l.has(e))return},o=!0,s;return n&&u.get(n).push(e),u.set(e,[]),r(),()=>{u.has(e)&&(s?.(),m(e))}};var T=t=>{let e=new Set,r=!0,o,s=()=>{if(r)return;r=!0;let x=e;e=new Set;for(let g of x)g()},b=()=>{for(;r;)r=!1,o=p(s,t);return o};return{get value(){return i(e),b()},peek(){return b()}}};var U=t=>{let e=new Set;return{get value(){return i(e),t},set value(r){if(t=r,c){let o=e;e=new Set;for(let s of o)s()}},peek(){return t}}};var z=t=>{let e=c;a(!1);try{return t()}finally{a(e)}};var H=t=>function(...r){let o,s=d(()=>{o??=t.apply(this,r)??this});return o[Symbol.dispose]=s,o};export{y as batch,T as computed,H as disposable,d as effect,U as signal,z as untracked};
1
+ var c=!0,a=t=>{c=t},n,i=t=>{c&&n&&t.add(n)},p=(t,e)=>{let r=n;n=t;try{return e()}finally{n=r}};var l=new WeakSet,u=new WeakMap,f,y=t=>{let e=f;e||(f=[]);try{return t()}finally{if(!e){[e,f]=[f,e];for(let[r,o]of e)l.has(r)||o()}}},b=t=>{let e=u.get(t);if(e.length)for(let o of e.splice(0))h(o)},h=t=>{l.add(t),b(t),u.delete(t)},m=t=>{let e=()=>{o||l.has(e)||(o=!0,n||(f?f.push([e,r]):r()))},r=()=>{for(;o;)if(o=!1,b(e),s?.(),s=p(e,t),l.has(e))return},o=!0,s;return n&&u.get(n).push(e),u.set(e,[]),r(),()=>{u.has(e)&&(s?.(),h(e))}};var T=t=>{let e=new Set,r=!0,o,s=()=>{for(;r;)r=!1,o=p(d,t);return o},d=()=>{if(r)return;r=!0;let x=e;e=new Set;for(let g of x)g()};return{get value(){return i(e),s()},peek:s}};var U=t=>{let e=new Set;return{get value(){return i(e),t},set value(r){if(t=r,c){let o=e;e=new Set;for(let s of o)s()}},peek(){return t}}};var z=t=>{let e=c;a(!1);try{return t()}finally{a(e)}};var H=t=>function(...r){let o,s=m(()=>{o??=t.apply(this,r)??this});return o[Symbol.dispose]=s,o};export{y as batch,T as computed,H as disposable,m as effect,U as signal,z as untracked};
package/dist/signals.js CHANGED
@@ -1 +1 @@
1
- var c=!0,p=t=>{c=t},s,i=t=>{c&&s&&t.add(s)},a=(t,e)=>{let r=s;s=t;try{return e()}finally{s=r}};var l=new WeakSet,u=new WeakMap,f,w=t=>{let e=f;e||(f=[]);try{return t()}finally{if(!e){[e,f]=[f,e];for(let[r,o]of e)l.has(r)||o()}}},h=t=>{let e=u.get(t);if(e.length)for(let o of e.splice(0))g(o)},g=t=>{l.add(t),h(t),u.delete(t)},v=t=>{let e=()=>{o||l.has(e)||(o=!0,s||(f?f.push([e,r]):r()))},r=()=>{for(;o;)if(o=!1,h(e),n?.(),n=a(e,t),l.has(e))return},o=!0,n;return s&&u.get(s).push(e),u.set(e,[]),r(),()=>{u.has(e)&&(n?.(),g(e))}};var T=t=>{let e=new Set,r=!0,o,n=()=>{if(r)return;r=!0;let x=e;e=new Set;for(let d of x)d()},b=()=>{for(;r;)r=!1,o=a(n,t);return o};return{get value(){return i(e),b()},peek(){return b()}}};var U=t=>{let e=new Set;return{get value(){return i(e),t},set value(r){if(t=r,c){let o=e;e=new Set;for(let n of o)n()}},peek(){return t}}};var z=t=>{let e=c;p(!1);try{return t()}finally{p(e)}};export{w as batch,T as computed,v as effect,U as signal,z as untracked};
1
+ var c=!0,p=t=>{c=t},s,a=t=>{c&&s&&t.add(s)},i=(t,e)=>{let r=s;s=t;try{return e()}finally{s=r}};var l=new WeakSet,u=new WeakMap,f,w=t=>{let e=f;e||(f=[]);try{return t()}finally{if(!e){[e,f]=[f,e];for(let[r,o]of e)l.has(r)||o()}}},b=t=>{let e=u.get(t);if(e.length)for(let o of e.splice(0))h(o)},h=t=>{l.add(t),b(t),u.delete(t)},v=t=>{let e=()=>{o||l.has(e)||(o=!0,s||(f?f.push([e,r]):r()))},r=()=>{for(;o;)if(o=!1,b(e),n?.(),n=i(e,t),l.has(e))return},o=!0,n;return s&&u.get(s).push(e),u.set(e,[]),r(),()=>{u.has(e)&&(n?.(),h(e))}};var T=t=>{let e=new Set,r=!0,o,n=()=>{for(;r;)r=!1,o=i(x,t);return o},x=()=>{if(r)return;r=!0;let d=e;e=new Set;for(let g of d)g()};return{get value(){return a(e),n()},peek:n}};var U=t=>{let e=new Set;return{get value(){return a(e),t},set value(r){if(t=r,c){let o=e;e=new Set;for(let n of o)n()}},peek(){return t}}};var z=t=>{let e=c;p(!1);try{return t()}finally{p(e)}};export{w as batch,T as computed,v as effect,U as signal,z as untracked};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webreflection/signals",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "A minimalistic Preact-like signals implementation",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
package/src/computed.js CHANGED
@@ -4,15 +4,7 @@ import { push, run } from './stack.js';
4
4
  export const computed = fn => {
5
5
  let subscribers = new Set, invalid = true, value;
6
6
 
7
- const subscriber = () => {
8
- if (invalid) return;
9
- invalid = true;
10
- const before = subscribers;
11
- subscribers = new Set;
12
- for (const subscriber of before) subscriber();
13
- };
14
-
15
- const get = () => {
7
+ const peek = () => {
16
8
  while (invalid) {
17
9
  invalid = false;
18
10
  value = run(subscriber, fn);
@@ -20,14 +12,20 @@ export const computed = fn => {
20
12
  return value;
21
13
  };
22
14
 
15
+ const subscriber = () => {
16
+ if (invalid) return;
17
+ invalid = true;
18
+ const before = subscribers;
19
+ subscribers = new Set;
20
+ for (const sub of before) sub();
21
+ };
22
+
23
23
  return {
24
24
  get value() {
25
25
  push(subscribers);
26
- return get();
26
+ return peek();
27
27
  },
28
28
 
29
- peek() {
30
- return get();
31
- },
29
+ peek,
32
30
  };
33
31
  };
package/src/signal.js CHANGED
@@ -15,7 +15,7 @@ export const signal = init => {
15
15
  if (tracking) {
16
16
  const before = subscribers;
17
17
  subscribers = new Set;
18
- for (const subscriber of before) subscriber();
18
+ for (const sub of before) sub();
19
19
  }
20
20
  },
21
21