totally-tubular 0.0.1-beta.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/.editorconfig +12 -0
- package/LICENSE +21 -0
- package/README.md +193 -0
- package/bun.lock +891 -0
- package/bun.lockb +0 -0
- package/commitlint.config.cjs +7 -0
- package/eslint.config.js +3 -0
- package/jsconfig.json +28 -0
- package/package.json +26 -0
- package/performanceTests.ts +485 -0
- package/src/totally-tubular.test.ts +104 -0
- package/src/totally-tubular.ts +113 -0
- package/src/types.ts +43 -0
- package/totally-tubular.jpg +0 -0
- package/tsconfig.json +33 -0
- package/vite.config.ts +3 -0
package/.editorconfig
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# top-most EditorConfig file
|
|
2
|
+
root = true
|
|
3
|
+
|
|
4
|
+
# Unix-style newlines with a newline ending every file
|
|
5
|
+
[*]
|
|
6
|
+
indent_style = space
|
|
7
|
+
indent_size = 2
|
|
8
|
+
end_of_line = lf
|
|
9
|
+
charset = utf-8
|
|
10
|
+
insert_final_newline = true
|
|
11
|
+
max_line_length = 100
|
|
12
|
+
trim_trailing_whitespace = true
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ben Duran
|
|
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,193 @@
|
|
|
1
|
+
# totally-tubular
|
|
2
|
+
|
|
3
|
+
A completely framework-agnostic, JavaScript / TypeScript state management and update broadcasting library wit zero dependencies, solid performance and a micro file size.
|
|
4
|
+
|
|
5
|
+
**Woahhhh**
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## But why is there _another_ state management library? I already use XYZ library.
|
|
10
|
+
|
|
11
|
+
Great! I'm glad you found something that works for you!
|
|
12
|
+
This library (`totally-tubular`) was an experiment that manifested out of feeling
|
|
13
|
+
like state management is overcomplicated these days and often riddled with "gotchas"
|
|
14
|
+
and easy-to-oopsie performance faux pas.
|
|
15
|
+
|
|
16
|
+
Additionally, our shipped JavaScript bundles should be getting **smaller**, not **bigger**,
|
|
17
|
+
but it's all-too-easy, especially when crafting enterprise software, to keep shoving more libraries
|
|
18
|
+
into your bundle with questionable benefits, when there often exists a much simpler, closer-to-vanilla
|
|
19
|
+
way to do what you need to do.
|
|
20
|
+
|
|
21
|
+
As a final point, your state management system should be fully-portable, so if you swap out
|
|
22
|
+
your rendering layer, be it React, Svelte, Angular, or something else, the meat-and-potatoes of
|
|
23
|
+
your state management shouldn't need to be fully rewritten.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
npm i totally-tubular --save
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Tubular } from "./src/totally-tubular.js";
|
|
35
|
+
|
|
36
|
+
interface MyStateShape {
|
|
37
|
+
isActive: boolean;
|
|
38
|
+
count: number;
|
|
39
|
+
meta: {
|
|
40
|
+
title: string;
|
|
41
|
+
};
|
|
42
|
+
name: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const initialState: MyStateShape = {
|
|
46
|
+
isActive: false,
|
|
47
|
+
count: 123,
|
|
48
|
+
meta: {
|
|
49
|
+
title: "Director",
|
|
50
|
+
},
|
|
51
|
+
name: "Test User",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const t = new Tubular(initialState);
|
|
55
|
+
|
|
56
|
+
// listen to any of the keys / values in your state object for changes
|
|
57
|
+
// and do whatever you need with the values
|
|
58
|
+
t.observe("meta.title", (newVal, oldVal, propPath) =>
|
|
59
|
+
console.info(propPath, "was updated from", oldVal, "to", newVal),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
console.info(t.read("meta.title")); // will print 'Director'
|
|
63
|
+
|
|
64
|
+
// update any of the keys / values in your state object.
|
|
65
|
+
// all observers will be notified after the value has been set
|
|
66
|
+
t.update("meta.title", (prev) => `Supreme ${prev}`);
|
|
67
|
+
console.info(t.read("meta.title")); // will print the updated 'Supreme Director' value
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### React example
|
|
71
|
+
|
|
72
|
+
Since React is one of the most popular UI libraries, here is how you might mimick the vanilla example, above, but in a React application.
|
|
73
|
+
|
|
74
|
+
**TODO**
|
|
75
|
+
|
|
76
|
+
## Things you should know
|
|
77
|
+
|
|
78
|
+
- This library _does not_ support observing arbitrary indices in an array, or objects inside of an array.
|
|
79
|
+
This is intentional.
|
|
80
|
+
If you want to be notified of changes to an array or you want to update some item in an array, you should use the path to the array in your state object, then perform reads or transformations on that.
|
|
81
|
+
- This library **mutates your originally-provided `initialState`**.
|
|
82
|
+
This is for performance reasons, as mutating objects doesn't require cloning and iterating on all object properties, saving on computation time.
|
|
83
|
+
If you want to ensure immutability, you should make your calls to `.update()` return new objects, array, etc.
|
|
84
|
+
|
|
85
|
+
## (Extra) Synthetic Performance Benchmarks
|
|
86
|
+
|
|
87
|
+
### Machine specs
|
|
88
|
+
|
|
89
|
+
#### Apple Machine
|
|
90
|
+
|
|
91
|
+
CPU: M3 Max
|
|
92
|
+
RAM: 36GB
|
|
93
|
+
OS: MacOS Sequoia 15.5
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
-----shallow state object-----
|
|
97
|
+
|
|
98
|
+
1,000 updates to "name" string val with 10 observers: 0.0036749580000000038s
|
|
99
|
+
1,000 updates to "name" string val with 100 observers: 0.0004760830000000027s
|
|
100
|
+
1,000 updates to "name" string val with 1000 observers: 0.002774291000000005s
|
|
101
|
+
10,000 updates to "name" string val with 10 observers: 0.0014766249999999986s
|
|
102
|
+
10,000 updates to "name" string val with 100 observers: 0.0023700000000000045s
|
|
103
|
+
10,000 updates to "name" string val with 1000 observers: 0.010886792s
|
|
104
|
+
100,000 updates to "name" string val with 10 observers: 0.012480834000000001s
|
|
105
|
+
100,000 updates to "name" string val with 100 observers: 0.022641292000000007s
|
|
106
|
+
100,000 updates to "name" string val with 1000 observers: 0.102325125s
|
|
107
|
+
1,000,000 updates to "name" string val with 10 observers: 0.16086666700000002s
|
|
108
|
+
1,000,000 updates to "name" string val with 100 observers: 0.26716437499999995s
|
|
109
|
+
1,000,000 updates to "name" string val with 1000 observers: 1.0726739170000001s
|
|
110
|
+
|
|
111
|
+
-----medium state object-----
|
|
112
|
+
|
|
113
|
+
1,000 updates to "user.settings.theme" string val with 10 observers: 0.000344333000000006s
|
|
114
|
+
1,000 updates to "user.settings.theme" string val with 100 observers: 0.00034458300000005695s
|
|
115
|
+
1,000 updates to "user.settings.theme" string val with 1000 observers: 0.0011382089999999608s
|
|
116
|
+
10,000 updates to "user.settings.theme" string val with 10 observers: 0.0012657919999999195s
|
|
117
|
+
10,000 updates to "user.settings.theme" string val with 100 observers: 0.002091625000000022s
|
|
118
|
+
10,000 updates to "user.settings.theme" string val with 1000 observers: 0.010777082999999948s
|
|
119
|
+
100,000 updates to "user.settings.theme" string val with 10 observers: 0.009487583000000086s
|
|
120
|
+
100,000 updates to "user.settings.theme" string val with 100 observers: 0.01902945799999998s
|
|
121
|
+
100,000 updates to "user.settings.theme" string val with 1000 observers: 0.11656079100000011s
|
|
122
|
+
1,000,000 updates to "user.settings.theme" string val with 10 observers: 0.14811841699999992s
|
|
123
|
+
1,000,000 updates to "user.settings.theme" string val with 100 observers: 0.27773633299999984s
|
|
124
|
+
1,000,000 updates to "user.settings.theme" string val with 1000 observers: 1.0240415829999998s
|
|
125
|
+
|
|
126
|
+
-----deep state object-----
|
|
127
|
+
|
|
128
|
+
1,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 10 observers: 0.0012417919999998049s
|
|
129
|
+
1,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 100 observers: 0.0003201250000001892s
|
|
130
|
+
1,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 1000 observers: 0.0011683750000001964s
|
|
131
|
+
10,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 10 observers: 0.0014113330000000133s
|
|
132
|
+
10,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 100 observers: 0.002289249999999811s
|
|
133
|
+
10,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 1000 observers: 0.01099350000000004s
|
|
134
|
+
100,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 10 observers: 0.014709209000000101s
|
|
135
|
+
100,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 100 observers: 0.024220374999999877s
|
|
136
|
+
100,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 1000 observers: 0.10777845800000023s
|
|
137
|
+
1,000,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 10 observers: 0.22398191699999961s
|
|
138
|
+
1,000,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 100 observers: 0.2829787500000002s
|
|
139
|
+
1,000,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 1000 observers: 1.1015419579999999s
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Windows Machine
|
|
143
|
+
|
|
144
|
+
CPU: Intel i9-13900HX
|
|
145
|
+
RAM: 32GB
|
|
146
|
+
OS: Windows 11
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
-----shallow state object-----
|
|
150
|
+
|
|
151
|
+
1,000 updates to "name" string val with 10 observers: 0.0046989000000000015s
|
|
152
|
+
1,000 updates to "name" string val with 100 observers: 0.0009328000000000003s
|
|
153
|
+
1,000 updates to "name" string val with 1000 observers: 0.0012964000000000055s
|
|
154
|
+
10,000 updates to "name" string val with 10 observers: 0.002852000000000004s
|
|
155
|
+
10,000 updates to "name" string val with 100 observers: 0.003517799999999994s
|
|
156
|
+
10,000 updates to "name" string val with 1000 observers: 0.012490899999999996s
|
|
157
|
+
100,000 updates to "name" string val with 10 observers: 0.0199987s
|
|
158
|
+
100,000 updates to "name" string val with 100 observers: 0.03123379999999999s
|
|
159
|
+
100,000 updates to "name" string val with 1000 observers: 0.1196829s
|
|
160
|
+
1,000,000 updates to "name" string val with 10 observers: 0.2171915s
|
|
161
|
+
1,000,000 updates to "name" string val with 100 observers: 0.24709929999999997s
|
|
162
|
+
1,000,000 updates to "name" string val with 1000 observers: 1.1660891999999998s
|
|
163
|
+
|
|
164
|
+
-----medium state object-----
|
|
165
|
+
|
|
166
|
+
1,000 updates to "user.settings.theme" string val with 10 observers: 0.0007828999999999269s
|
|
167
|
+
1,000 updates to "user.settings.theme" string val with 100 observers: 0.0004992999999999483s
|
|
168
|
+
1,000 updates to "user.settings.theme" string val with 1000 observers: 0.0013275000000001s
|
|
169
|
+
10,000 updates to "user.settings.theme" string val with 10 observers: 0.0032917999999999667s
|
|
170
|
+
10,000 updates to "user.settings.theme" string val with 100 observers: 0.004183099999999967s
|
|
171
|
+
10,000 updates to "user.settings.theme" string val with 1000 observers: 0.012704099999999927s
|
|
172
|
+
100,000 updates to "user.settings.theme" string val with 10 observers: 0.023384700000000067s
|
|
173
|
+
100,000 updates to "user.settings.theme" string val with 100 observers: 0.03393959999999993s
|
|
174
|
+
100,000 updates to "user.settings.theme" string val with 1000 observers: 0.1242770999999998s
|
|
175
|
+
1,000,000 updates to "user.settings.theme" string val with 10 observers: 0.23742110000000002s
|
|
176
|
+
1,000,000 updates to "user.settings.theme" string val with 100 observers: 0.26894860000000015s
|
|
177
|
+
1,000,000 updates to "user.settings.theme" string val with 1000 observers: 1.2531396s
|
|
178
|
+
|
|
179
|
+
-----deep state object-----
|
|
180
|
+
|
|
181
|
+
1,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 10 observers: 0.0003414999999999964s
|
|
182
|
+
1,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 100 observers: 0.00033490000000028884s
|
|
183
|
+
1,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 1000 observers: 0.0016161000000001877s
|
|
184
|
+
10,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 10 observers: 0.0022989000000002307s
|
|
185
|
+
10,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 100 observers: 0.0037910000000001675s
|
|
186
|
+
10,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 1000 observers: 0.014288300000000163s
|
|
187
|
+
100,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 10 observers: 0.025205199999999876s
|
|
188
|
+
100,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 100 observers: 0.03601400000000012s
|
|
189
|
+
100,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 1000 observers: 0.13781609999999955s
|
|
190
|
+
1,000,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 10 observers: 0.27891269999999985s
|
|
191
|
+
1,000,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 100 observers: 0.36865729999999985s
|
|
192
|
+
1,000,000 updates to "zdeepSettings.level1.level2.level3.level4.level5.level6.currentValue" string val with 1000 observers: 1.3965289000000003s
|
|
193
|
+
```
|