flatsignals 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/index.cjs +222 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +209 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 kevintakeda
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# FlatSignals
|
|
2
|
+
|
|
3
|
+
FlatSignals is an extremely fast reactivity library (~0.7kb).
|
|
4
|
+
|
|
5
|
+
- Lightning-fast batch updates.
|
|
6
|
+
- No graph traversals.
|
|
7
|
+
- Dynamic dependencies managed in O(1) time.
|
|
8
|
+
- Automatic disposals.
|
|
9
|
+
- Computeds are lazy by default.
|
|
10
|
+
|
|
11
|
+
## Benchmarks
|
|
12
|
+
|
|
13
|
+
You can execute the [benchmarks](https://github.com/kevintakeda/flatsignals/tree/main/benchmarks) by running `pnpm bench`.
|
|
14
|
+
|
|
15
|
+
```console
|
|
16
|
+
flatsignals - benchmarks/signals.bench.ts > one to many sparse (32x)
|
|
17
|
+
1.60x faster than @preact/signals
|
|
18
|
+
1.70x faster than @reactively/core
|
|
19
|
+
1.95x faster than alien-signals
|
|
20
|
+
2.27x faster than @maverick-js/signals
|
|
21
|
+
|
|
22
|
+
flatsignals - benchmarks/signals.bench.ts > wide propagation (32x)
|
|
23
|
+
1.72x faster than @preact/signals
|
|
24
|
+
1.74x faster than alien-signals
|
|
25
|
+
2.22x faster than @reactively/core
|
|
26
|
+
2.94x faster than @maverick-js/signals
|
|
27
|
+
|
|
28
|
+
flatsignals - benchmarks/signals.bench.ts > deep propagation (32x)
|
|
29
|
+
1.02x faster than alien-signals
|
|
30
|
+
1.29x faster than @preact/signals
|
|
31
|
+
1.55x faster than @reactively/core
|
|
32
|
+
1.97x faster than @maverick-js/signals
|
|
33
|
+
|
|
34
|
+
flatsignals - benchmarks/signals.bench.ts > dynamic
|
|
35
|
+
2.82x faster than @preact/signals
|
|
36
|
+
2.98x faster than @reactively/core
|
|
37
|
+
3.54x faster than @maverick-js/signals
|
|
38
|
+
4.56x faster than alien-signals
|
|
39
|
+
|
|
40
|
+
flatsignals - benchmarks/signals.bench.ts > batch 25%
|
|
41
|
+
1.26x faster than @preact/signals
|
|
42
|
+
1.56x faster than alien-signals
|
|
43
|
+
1.66x faster than @reactively/core
|
|
44
|
+
1.97x faster than @maverick-js/signals
|
|
45
|
+
|
|
46
|
+
flatsignals - benchmarks/signals.bench.ts > batch 50%
|
|
47
|
+
1.48x faster than @preact/signals
|
|
48
|
+
1.62x faster than alien-signals
|
|
49
|
+
1.72x faster than @reactively/core
|
|
50
|
+
2.09x faster than @maverick-js/signals
|
|
51
|
+
|
|
52
|
+
flatsignals - benchmarks/signals.bench.ts > packed 30%
|
|
53
|
+
1.49x faster than @reactively/core
|
|
54
|
+
1.77x faster than alien-signals
|
|
55
|
+
1.77x faster than @preact/signals
|
|
56
|
+
1.99x faster than @maverick-js/signals
|
|
57
|
+
|
|
58
|
+
flatsignals - benchmarks/signals.bench.ts > dense batch ~1/3 (2x layers)
|
|
59
|
+
1.72x faster than @preact/signals
|
|
60
|
+
2.17x faster than @reactively/core
|
|
61
|
+
2.25x faster than alien-signals
|
|
62
|
+
2.86x faster than @maverick-js/signals
|
|
63
|
+
|
|
64
|
+
flatsignals - benchmarks/signals.bench.ts > dense batch ~1/3 (4x layers)
|
|
65
|
+
1.98x faster than @preact/signals
|
|
66
|
+
2.11x faster than @reactively/core
|
|
67
|
+
2.75x faster than alien-signals
|
|
68
|
+
3.05x faster than @maverick-js/signals
|
|
69
|
+
|
|
70
|
+
alien-signals - benchmarks/signals.bench.ts > one to one to one (32x)
|
|
71
|
+
1.00x faster than @preact/signals
|
|
72
|
+
1.05x faster than flatsignals
|
|
73
|
+
1.16x faster than @maverick-js/signals
|
|
74
|
+
1.94x faster than @reactively/core
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Tradeoffs
|
|
78
|
+
|
|
79
|
+
1. Supports only 32 signals per root.
|
|
80
|
+
2. Set operations have 𝑂(𝑁) complexity, where 𝑁 is the number of computations. However, multiple updates can be batched into a single operation.
|
|
81
|
+
3. When data sources change, all dependent nodes are marked dirty, even if intermediate values stay the same.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
var ROOT = null;
|
|
5
|
+
var COMPUTED = null;
|
|
6
|
+
var BATCHING = false;
|
|
7
|
+
var ROOT_QUEUE = [];
|
|
8
|
+
var Scope = class {
|
|
9
|
+
/* @internal */
|
|
10
|
+
_disposing = false;
|
|
11
|
+
/* @internal */
|
|
12
|
+
_disposals = null;
|
|
13
|
+
constructor() {
|
|
14
|
+
getScope()?._onDispose(this._dispose.bind(this));
|
|
15
|
+
}
|
|
16
|
+
/* @internal */
|
|
17
|
+
_dispose() {
|
|
18
|
+
if (this._disposing) return;
|
|
19
|
+
this._disposing = true;
|
|
20
|
+
this._disposals?.forEach((fn) => fn());
|
|
21
|
+
this._disposing = false;
|
|
22
|
+
}
|
|
23
|
+
/* @internal */
|
|
24
|
+
_onDispose(fn) {
|
|
25
|
+
if (!this._disposals) this._disposals = [];
|
|
26
|
+
this._disposals.push(fn);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var Root = class extends Scope {
|
|
30
|
+
/* @internal computeds */
|
|
31
|
+
_c = [];
|
|
32
|
+
/* @internal disposed computeds */
|
|
33
|
+
_x = [];
|
|
34
|
+
/* @internal id generator */
|
|
35
|
+
_i = 0;
|
|
36
|
+
/* @internal batch mask */
|
|
37
|
+
#batch = 0;
|
|
38
|
+
/* @internal */
|
|
39
|
+
_dispose() {
|
|
40
|
+
super._dispose();
|
|
41
|
+
this._c = [];
|
|
42
|
+
this._x = [];
|
|
43
|
+
this._i = 0;
|
|
44
|
+
}
|
|
45
|
+
/* @internal Add source */
|
|
46
|
+
_as() {
|
|
47
|
+
return this._i++ % 32;
|
|
48
|
+
}
|
|
49
|
+
/* @internal Add computed */
|
|
50
|
+
_ac(c) {
|
|
51
|
+
if (this._x.length) {
|
|
52
|
+
const u = this._x.pop();
|
|
53
|
+
this._c[u] = c;
|
|
54
|
+
return u;
|
|
55
|
+
} else {
|
|
56
|
+
return this._c.push(c) - 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/* @internal Destroy computed */
|
|
60
|
+
_dc(idx) {
|
|
61
|
+
this._x.push(idx);
|
|
62
|
+
}
|
|
63
|
+
/* @internal */
|
|
64
|
+
_queue(mask) {
|
|
65
|
+
if (!this.#batch) {
|
|
66
|
+
ROOT_QUEUE.push(this);
|
|
67
|
+
}
|
|
68
|
+
this.#batch |= mask;
|
|
69
|
+
if (!BATCHING) this._flush(true);
|
|
70
|
+
}
|
|
71
|
+
/* @internal */
|
|
72
|
+
_flush(force = false) {
|
|
73
|
+
if (!this.#batch || !force) return;
|
|
74
|
+
for (const item of this._c) {
|
|
75
|
+
if (!item._dirty && (item._sources & this.#batch) !== 0) {
|
|
76
|
+
item._dirty = true;
|
|
77
|
+
if (item._effect) item.val;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.#batch = 0;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var DataSignal = class {
|
|
84
|
+
#root;
|
|
85
|
+
#val;
|
|
86
|
+
#id = 0;
|
|
87
|
+
equals = defaultEquality;
|
|
88
|
+
constructor(val) {
|
|
89
|
+
if (!ROOT) ROOT = new Root();
|
|
90
|
+
this.#root = ROOT;
|
|
91
|
+
this.#val = val;
|
|
92
|
+
this.#id |= 1 << this.#root._as();
|
|
93
|
+
}
|
|
94
|
+
get val() {
|
|
95
|
+
if (COMPUTED) {
|
|
96
|
+
COMPUTED._sources |= this.#id;
|
|
97
|
+
}
|
|
98
|
+
return this.#val;
|
|
99
|
+
}
|
|
100
|
+
set val(val) {
|
|
101
|
+
if (this.equals(val, this.#val)) return;
|
|
102
|
+
this.#val = val;
|
|
103
|
+
this.#root._queue(this.#id);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var Computation = class extends Scope {
|
|
107
|
+
#root;
|
|
108
|
+
#id;
|
|
109
|
+
#val;
|
|
110
|
+
#fn;
|
|
111
|
+
/* @internal */
|
|
112
|
+
_effect = false;
|
|
113
|
+
/* @internal */
|
|
114
|
+
_sources = 0;
|
|
115
|
+
/* @internal */
|
|
116
|
+
_dirty = true;
|
|
117
|
+
/* @internal destroyed */
|
|
118
|
+
_d = false;
|
|
119
|
+
constructor(compute, val, effect2) {
|
|
120
|
+
if (!ROOT) ROOT = new Root();
|
|
121
|
+
super();
|
|
122
|
+
this.#root = ROOT;
|
|
123
|
+
this.#fn = compute;
|
|
124
|
+
this.#val = val;
|
|
125
|
+
this.#id = this.#root._ac(this);
|
|
126
|
+
if (effect2) {
|
|
127
|
+
this._effect = effect2;
|
|
128
|
+
this.val;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
get val() {
|
|
132
|
+
this.#root._flush();
|
|
133
|
+
const prevCurrent = COMPUTED;
|
|
134
|
+
if (this._dirty) {
|
|
135
|
+
super._dispose();
|
|
136
|
+
COMPUTED = this;
|
|
137
|
+
this._sources = 0;
|
|
138
|
+
this.#val = this.#fn();
|
|
139
|
+
this._dirty = false;
|
|
140
|
+
COMPUTED = prevCurrent;
|
|
141
|
+
}
|
|
142
|
+
if (prevCurrent) {
|
|
143
|
+
prevCurrent._sources |= this._sources;
|
|
144
|
+
}
|
|
145
|
+
return this.#val;
|
|
146
|
+
}
|
|
147
|
+
get peek() {
|
|
148
|
+
return this.#val;
|
|
149
|
+
}
|
|
150
|
+
getRoot() {
|
|
151
|
+
return this.#root;
|
|
152
|
+
}
|
|
153
|
+
/* @internal */
|
|
154
|
+
_dispose() {
|
|
155
|
+
if (this._d) return;
|
|
156
|
+
super._dispose();
|
|
157
|
+
this._sources = 0;
|
|
158
|
+
this._dirty = false;
|
|
159
|
+
this._d = true;
|
|
160
|
+
this.#root._dc(this.#id);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
function defaultEquality(a, b) {
|
|
164
|
+
return a === b;
|
|
165
|
+
}
|
|
166
|
+
function getScope() {
|
|
167
|
+
return COMPUTED || ROOT;
|
|
168
|
+
}
|
|
169
|
+
function withScope(scope, fn) {
|
|
170
|
+
const prevComputed = COMPUTED, prevRoot = ROOT;
|
|
171
|
+
if (scope instanceof Computation) COMPUTED = scope;
|
|
172
|
+
ROOT = COMPUTED?.getRoot() ?? ROOT;
|
|
173
|
+
const result = fn();
|
|
174
|
+
COMPUTED = prevComputed;
|
|
175
|
+
ROOT = prevRoot;
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
function onDispose(fn) {
|
|
179
|
+
getScope()?._onDispose(fn);
|
|
180
|
+
}
|
|
181
|
+
function batch(fn) {
|
|
182
|
+
if (BATCHING) return fn();
|
|
183
|
+
BATCHING = true;
|
|
184
|
+
fn();
|
|
185
|
+
if (ROOT_QUEUE.length) {
|
|
186
|
+
for (const el of ROOT_QUEUE) el._flush(true);
|
|
187
|
+
ROOT_QUEUE = [];
|
|
188
|
+
}
|
|
189
|
+
BATCHING = false;
|
|
190
|
+
}
|
|
191
|
+
function root(fn, existingRoot) {
|
|
192
|
+
const prevRoot = ROOT;
|
|
193
|
+
const prevScope = getScope();
|
|
194
|
+
ROOT = existingRoot ?? new Root();
|
|
195
|
+
prevScope?._onDispose(ROOT._dispose.bind(ROOT));
|
|
196
|
+
const result = fn(ROOT._dispose.bind(ROOT));
|
|
197
|
+
ROOT = prevRoot;
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
function signal(value) {
|
|
201
|
+
return new DataSignal(value);
|
|
202
|
+
}
|
|
203
|
+
function computed(val) {
|
|
204
|
+
return new Computation(val);
|
|
205
|
+
}
|
|
206
|
+
function effect(fn) {
|
|
207
|
+
const sig = new Computation(fn, void 0, true);
|
|
208
|
+
return sig._dispose.bind(sig);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
exports.Computation = Computation;
|
|
212
|
+
exports.DataSignal = DataSignal;
|
|
213
|
+
exports.Root = Root;
|
|
214
|
+
exports.Scope = Scope;
|
|
215
|
+
exports.batch = batch;
|
|
216
|
+
exports.computed = computed;
|
|
217
|
+
exports.effect = effect;
|
|
218
|
+
exports.getScope = getScope;
|
|
219
|
+
exports.onDispose = onDispose;
|
|
220
|
+
exports.root = root;
|
|
221
|
+
exports.signal = signal;
|
|
222
|
+
exports.withScope = withScope;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
declare class Scope {
|
|
2
|
+
constructor();
|
|
3
|
+
}
|
|
4
|
+
declare class Root extends Scope {
|
|
5
|
+
#private;
|
|
6
|
+
}
|
|
7
|
+
declare class DataSignal<T = any> {
|
|
8
|
+
#private;
|
|
9
|
+
equals: typeof defaultEquality;
|
|
10
|
+
constructor(val?: T);
|
|
11
|
+
get val(): T;
|
|
12
|
+
set val(val: T);
|
|
13
|
+
}
|
|
14
|
+
declare class Computation<T = unknown> extends Scope {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(compute?: () => T, val?: T, effect?: boolean);
|
|
17
|
+
get val(): T;
|
|
18
|
+
get peek(): T;
|
|
19
|
+
getRoot(): Root;
|
|
20
|
+
}
|
|
21
|
+
declare function defaultEquality(a: unknown, b: unknown): boolean;
|
|
22
|
+
declare function getScope(): Scope | null;
|
|
23
|
+
declare function withScope(scope: Scope, fn: () => void): void;
|
|
24
|
+
declare function onDispose(fn: () => void): void;
|
|
25
|
+
declare function batch(fn: () => void): void;
|
|
26
|
+
declare function root<T>(fn: (dispose: () => void) => T, existingRoot?: Root): T;
|
|
27
|
+
declare function signal<T>(value: T): DataSignal<T>;
|
|
28
|
+
declare function signal<T = undefined>(): DataSignal<T | undefined>;
|
|
29
|
+
declare function computed<T>(val: () => T): Computation<T>;
|
|
30
|
+
declare function effect<T = unknown>(fn: () => T): () => void;
|
|
31
|
+
|
|
32
|
+
export { Computation, DataSignal, Root, Scope, batch, computed, effect, getScope, onDispose, root, signal, withScope };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
declare class Scope {
|
|
2
|
+
constructor();
|
|
3
|
+
}
|
|
4
|
+
declare class Root extends Scope {
|
|
5
|
+
#private;
|
|
6
|
+
}
|
|
7
|
+
declare class DataSignal<T = any> {
|
|
8
|
+
#private;
|
|
9
|
+
equals: typeof defaultEquality;
|
|
10
|
+
constructor(val?: T);
|
|
11
|
+
get val(): T;
|
|
12
|
+
set val(val: T);
|
|
13
|
+
}
|
|
14
|
+
declare class Computation<T = unknown> extends Scope {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(compute?: () => T, val?: T, effect?: boolean);
|
|
17
|
+
get val(): T;
|
|
18
|
+
get peek(): T;
|
|
19
|
+
getRoot(): Root;
|
|
20
|
+
}
|
|
21
|
+
declare function defaultEquality(a: unknown, b: unknown): boolean;
|
|
22
|
+
declare function getScope(): Scope | null;
|
|
23
|
+
declare function withScope(scope: Scope, fn: () => void): void;
|
|
24
|
+
declare function onDispose(fn: () => void): void;
|
|
25
|
+
declare function batch(fn: () => void): void;
|
|
26
|
+
declare function root<T>(fn: (dispose: () => void) => T, existingRoot?: Root): T;
|
|
27
|
+
declare function signal<T>(value: T): DataSignal<T>;
|
|
28
|
+
declare function signal<T = undefined>(): DataSignal<T | undefined>;
|
|
29
|
+
declare function computed<T>(val: () => T): Computation<T>;
|
|
30
|
+
declare function effect<T = unknown>(fn: () => T): () => void;
|
|
31
|
+
|
|
32
|
+
export { Computation, DataSignal, Root, Scope, batch, computed, effect, getScope, onDispose, root, signal, withScope };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var ROOT = null;
|
|
3
|
+
var COMPUTED = null;
|
|
4
|
+
var BATCHING = false;
|
|
5
|
+
var ROOT_QUEUE = [];
|
|
6
|
+
var Scope = class {
|
|
7
|
+
/* @internal */
|
|
8
|
+
_disposing = false;
|
|
9
|
+
/* @internal */
|
|
10
|
+
_disposals = null;
|
|
11
|
+
constructor() {
|
|
12
|
+
getScope()?._onDispose(this._dispose.bind(this));
|
|
13
|
+
}
|
|
14
|
+
/* @internal */
|
|
15
|
+
_dispose() {
|
|
16
|
+
if (this._disposing) return;
|
|
17
|
+
this._disposing = true;
|
|
18
|
+
this._disposals?.forEach((fn) => fn());
|
|
19
|
+
this._disposing = false;
|
|
20
|
+
}
|
|
21
|
+
/* @internal */
|
|
22
|
+
_onDispose(fn) {
|
|
23
|
+
if (!this._disposals) this._disposals = [];
|
|
24
|
+
this._disposals.push(fn);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var Root = class extends Scope {
|
|
28
|
+
/* @internal computeds */
|
|
29
|
+
_c = [];
|
|
30
|
+
/* @internal disposed computeds */
|
|
31
|
+
_x = [];
|
|
32
|
+
/* @internal id generator */
|
|
33
|
+
_i = 0;
|
|
34
|
+
/* @internal batch mask */
|
|
35
|
+
#batch = 0;
|
|
36
|
+
/* @internal */
|
|
37
|
+
_dispose() {
|
|
38
|
+
super._dispose();
|
|
39
|
+
this._c = [];
|
|
40
|
+
this._x = [];
|
|
41
|
+
this._i = 0;
|
|
42
|
+
}
|
|
43
|
+
/* @internal Add source */
|
|
44
|
+
_as() {
|
|
45
|
+
return this._i++ % 32;
|
|
46
|
+
}
|
|
47
|
+
/* @internal Add computed */
|
|
48
|
+
_ac(c) {
|
|
49
|
+
if (this._x.length) {
|
|
50
|
+
const u = this._x.pop();
|
|
51
|
+
this._c[u] = c;
|
|
52
|
+
return u;
|
|
53
|
+
} else {
|
|
54
|
+
return this._c.push(c) - 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/* @internal Destroy computed */
|
|
58
|
+
_dc(idx) {
|
|
59
|
+
this._x.push(idx);
|
|
60
|
+
}
|
|
61
|
+
/* @internal */
|
|
62
|
+
_queue(mask) {
|
|
63
|
+
if (!this.#batch) {
|
|
64
|
+
ROOT_QUEUE.push(this);
|
|
65
|
+
}
|
|
66
|
+
this.#batch |= mask;
|
|
67
|
+
if (!BATCHING) this._flush(true);
|
|
68
|
+
}
|
|
69
|
+
/* @internal */
|
|
70
|
+
_flush(force = false) {
|
|
71
|
+
if (!this.#batch || !force) return;
|
|
72
|
+
for (const item of this._c) {
|
|
73
|
+
if (!item._dirty && (item._sources & this.#batch) !== 0) {
|
|
74
|
+
item._dirty = true;
|
|
75
|
+
if (item._effect) item.val;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
this.#batch = 0;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var DataSignal = class {
|
|
82
|
+
#root;
|
|
83
|
+
#val;
|
|
84
|
+
#id = 0;
|
|
85
|
+
equals = defaultEquality;
|
|
86
|
+
constructor(val) {
|
|
87
|
+
if (!ROOT) ROOT = new Root();
|
|
88
|
+
this.#root = ROOT;
|
|
89
|
+
this.#val = val;
|
|
90
|
+
this.#id |= 1 << this.#root._as();
|
|
91
|
+
}
|
|
92
|
+
get val() {
|
|
93
|
+
if (COMPUTED) {
|
|
94
|
+
COMPUTED._sources |= this.#id;
|
|
95
|
+
}
|
|
96
|
+
return this.#val;
|
|
97
|
+
}
|
|
98
|
+
set val(val) {
|
|
99
|
+
if (this.equals(val, this.#val)) return;
|
|
100
|
+
this.#val = val;
|
|
101
|
+
this.#root._queue(this.#id);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var Computation = class extends Scope {
|
|
105
|
+
#root;
|
|
106
|
+
#id;
|
|
107
|
+
#val;
|
|
108
|
+
#fn;
|
|
109
|
+
/* @internal */
|
|
110
|
+
_effect = false;
|
|
111
|
+
/* @internal */
|
|
112
|
+
_sources = 0;
|
|
113
|
+
/* @internal */
|
|
114
|
+
_dirty = true;
|
|
115
|
+
/* @internal destroyed */
|
|
116
|
+
_d = false;
|
|
117
|
+
constructor(compute, val, effect2) {
|
|
118
|
+
if (!ROOT) ROOT = new Root();
|
|
119
|
+
super();
|
|
120
|
+
this.#root = ROOT;
|
|
121
|
+
this.#fn = compute;
|
|
122
|
+
this.#val = val;
|
|
123
|
+
this.#id = this.#root._ac(this);
|
|
124
|
+
if (effect2) {
|
|
125
|
+
this._effect = effect2;
|
|
126
|
+
this.val;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
get val() {
|
|
130
|
+
this.#root._flush();
|
|
131
|
+
const prevCurrent = COMPUTED;
|
|
132
|
+
if (this._dirty) {
|
|
133
|
+
super._dispose();
|
|
134
|
+
COMPUTED = this;
|
|
135
|
+
this._sources = 0;
|
|
136
|
+
this.#val = this.#fn();
|
|
137
|
+
this._dirty = false;
|
|
138
|
+
COMPUTED = prevCurrent;
|
|
139
|
+
}
|
|
140
|
+
if (prevCurrent) {
|
|
141
|
+
prevCurrent._sources |= this._sources;
|
|
142
|
+
}
|
|
143
|
+
return this.#val;
|
|
144
|
+
}
|
|
145
|
+
get peek() {
|
|
146
|
+
return this.#val;
|
|
147
|
+
}
|
|
148
|
+
getRoot() {
|
|
149
|
+
return this.#root;
|
|
150
|
+
}
|
|
151
|
+
/* @internal */
|
|
152
|
+
_dispose() {
|
|
153
|
+
if (this._d) return;
|
|
154
|
+
super._dispose();
|
|
155
|
+
this._sources = 0;
|
|
156
|
+
this._dirty = false;
|
|
157
|
+
this._d = true;
|
|
158
|
+
this.#root._dc(this.#id);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
function defaultEquality(a, b) {
|
|
162
|
+
return a === b;
|
|
163
|
+
}
|
|
164
|
+
function getScope() {
|
|
165
|
+
return COMPUTED || ROOT;
|
|
166
|
+
}
|
|
167
|
+
function withScope(scope, fn) {
|
|
168
|
+
const prevComputed = COMPUTED, prevRoot = ROOT;
|
|
169
|
+
if (scope instanceof Computation) COMPUTED = scope;
|
|
170
|
+
ROOT = COMPUTED?.getRoot() ?? ROOT;
|
|
171
|
+
const result = fn();
|
|
172
|
+
COMPUTED = prevComputed;
|
|
173
|
+
ROOT = prevRoot;
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
function onDispose(fn) {
|
|
177
|
+
getScope()?._onDispose(fn);
|
|
178
|
+
}
|
|
179
|
+
function batch(fn) {
|
|
180
|
+
if (BATCHING) return fn();
|
|
181
|
+
BATCHING = true;
|
|
182
|
+
fn();
|
|
183
|
+
if (ROOT_QUEUE.length) {
|
|
184
|
+
for (const el of ROOT_QUEUE) el._flush(true);
|
|
185
|
+
ROOT_QUEUE = [];
|
|
186
|
+
}
|
|
187
|
+
BATCHING = false;
|
|
188
|
+
}
|
|
189
|
+
function root(fn, existingRoot) {
|
|
190
|
+
const prevRoot = ROOT;
|
|
191
|
+
const prevScope = getScope();
|
|
192
|
+
ROOT = existingRoot ?? new Root();
|
|
193
|
+
prevScope?._onDispose(ROOT._dispose.bind(ROOT));
|
|
194
|
+
const result = fn(ROOT._dispose.bind(ROOT));
|
|
195
|
+
ROOT = prevRoot;
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
function signal(value) {
|
|
199
|
+
return new DataSignal(value);
|
|
200
|
+
}
|
|
201
|
+
function computed(val) {
|
|
202
|
+
return new Computation(val);
|
|
203
|
+
}
|
|
204
|
+
function effect(fn) {
|
|
205
|
+
const sig = new Computation(fn, void 0, true);
|
|
206
|
+
return sig._dispose.bind(sig);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { Computation, DataSignal, Root, Scope, batch, computed, effect, getScope, onDispose, root, signal, withScope };
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flatsignals",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "FlatSignals is an extremely fast reactivity library (~0.7kb)",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"bench": "vitest bench",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"size": "size-limit"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@maverick-js/signals": "^6.0.0",
|
|
23
|
+
"@preact/signals-core": "^1.12.1",
|
|
24
|
+
"@reactively/core": "^0.0.8",
|
|
25
|
+
"@size-limit/preset-small-lib": "^11.2.0",
|
|
26
|
+
"@types/node": "^22.18.11",
|
|
27
|
+
"alien-signals": "^3.0.1",
|
|
28
|
+
"size-limit": "^11.2.0",
|
|
29
|
+
"tsup": "^8.5.0",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vitest": "^2.1.9"
|
|
32
|
+
},
|
|
33
|
+
"size-limit": [
|
|
34
|
+
{
|
|
35
|
+
"path": "dist/index.js",
|
|
36
|
+
"limit": "1 kB"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|