@esportsplus/reactivity 0.0.1

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 ADDED
@@ -0,0 +1,9 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 4
6
+ charset = utf-8
7
+ trim_trailing_whitespace = true
8
+ insert_final_newline = true
9
+ end_of_line = lf
package/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -0,0 +1,20 @@
1
+ declare class Reactive<T> {
2
+ private effect;
3
+ private fn?;
4
+ private observers;
5
+ private sources;
6
+ private state;
7
+ private value;
8
+ cleanup: ((old: T) => void)[] | null;
9
+ constructor(_: ((fn: VoidFunction) => T) | T, effect?: boolean);
10
+ get(): T;
11
+ set(value: T): void;
12
+ private mark;
13
+ private removeParentObservers;
14
+ private sync;
15
+ private update;
16
+ }
17
+ declare const effect: <T>(value: () => T) => Reactive<T>;
18
+ declare const reactive: <T>(value: T) => {};
19
+ declare const tick: () => void;
20
+ export { effect, reactive, tick };
package/build/index.js ADDED
@@ -0,0 +1,176 @@
1
+ import { CLEAN, CHECK, DIRTY } from './symbols';
2
+ import obj from './obj';
3
+ let index = 0, queue = [], reaction = null, running = false, stack = null;
4
+ class Reactive {
5
+ effect;
6
+ fn;
7
+ observers = null;
8
+ sources = null;
9
+ state;
10
+ value;
11
+ cleanup = null;
12
+ constructor(_, effect = false) {
13
+ this.effect = effect;
14
+ if (typeof _ === 'function') {
15
+ this.fn = _;
16
+ this.state = DIRTY;
17
+ this.value = undefined;
18
+ if (effect) {
19
+ this.update();
20
+ }
21
+ }
22
+ else {
23
+ this.state = CLEAN;
24
+ this.value = _;
25
+ }
26
+ }
27
+ get() {
28
+ if (reaction) {
29
+ if (!stack && reaction.sources && reaction.sources[index] == this) {
30
+ index++;
31
+ }
32
+ else {
33
+ if (!stack) {
34
+ stack = [this];
35
+ }
36
+ else {
37
+ stack.push(this);
38
+ }
39
+ }
40
+ }
41
+ if (this.fn) {
42
+ this.sync();
43
+ }
44
+ return this.value;
45
+ }
46
+ set(value) {
47
+ if (this.observers && this.value !== value) {
48
+ for (let i = 0; i < this.observers.length; i++) {
49
+ this.observers[i].mark(DIRTY);
50
+ }
51
+ }
52
+ this.value = value;
53
+ }
54
+ mark(state) {
55
+ if (this.state < state) {
56
+ if (this.state === CLEAN && this.effect) {
57
+ queue.push(this);
58
+ }
59
+ this.state = state;
60
+ if (!this.observers) {
61
+ return;
62
+ }
63
+ for (let i = 0; i < this.observers.length; i++) {
64
+ this.observers[i].mark(CHECK);
65
+ }
66
+ }
67
+ }
68
+ removeParentObservers() {
69
+ if (!this.sources) {
70
+ return;
71
+ }
72
+ for (let i = index; i < this.sources.length; i++) {
73
+ let source = this.sources[i];
74
+ source.observers[source.observers.findIndex((v) => v === this)] = source.observers[source.observers.length - 1];
75
+ source.observers.pop();
76
+ }
77
+ }
78
+ sync() {
79
+ if (this.state === CHECK && this.sources) {
80
+ for (let i = 0, n = this.sources.length; i < n; i++) {
81
+ this.sources[i].sync();
82
+ if (this.state === DIRTY) {
83
+ break;
84
+ }
85
+ }
86
+ }
87
+ if (this.state === DIRTY) {
88
+ this.update();
89
+ }
90
+ this.state = CLEAN;
91
+ }
92
+ update() {
93
+ let previous = {
94
+ index: index,
95
+ reaction: reaction,
96
+ stack: stack,
97
+ value: this.value
98
+ };
99
+ index = 0;
100
+ reaction = this;
101
+ stack = [];
102
+ try {
103
+ if (this.cleanup) {
104
+ for (let i = 0, n = this.cleanup.length; i < n; i++) {
105
+ this.cleanup[i](this.value);
106
+ }
107
+ this.cleanup.length = 0;
108
+ }
109
+ this.value = this.fn((fn) => {
110
+ if (!this.cleanup) {
111
+ this.cleanup = [fn];
112
+ }
113
+ else {
114
+ this.cleanup.push(fn);
115
+ }
116
+ });
117
+ if (stack.length) {
118
+ this.removeParentObservers();
119
+ if (this.sources && index > 0) {
120
+ this.sources.length = index + stack.length;
121
+ for (let i = 0; i < stack.length; i++) {
122
+ this.sources[index + i] = stack[i];
123
+ }
124
+ }
125
+ else {
126
+ this.sources = stack;
127
+ }
128
+ for (let i = index; i < this.sources.length; i++) {
129
+ let source = this.sources[i];
130
+ if (!source.observers) {
131
+ source.observers = [this];
132
+ }
133
+ else {
134
+ source.observers.push(this);
135
+ }
136
+ }
137
+ }
138
+ else if (this.sources && index < this.sources.length) {
139
+ this.removeParentObservers();
140
+ this.sources.length = index;
141
+ }
142
+ }
143
+ finally {
144
+ index = previous.index;
145
+ reaction = previous.reaction;
146
+ stack = previous.stack;
147
+ }
148
+ if (this.observers && previous.value !== this.value) {
149
+ for (let i = 0; i < this.observers.length; i++) {
150
+ this.observers[i].state = DIRTY;
151
+ }
152
+ }
153
+ this.state = CLEAN;
154
+ }
155
+ }
156
+ const effect = (value) => {
157
+ return new Reactive(value, true);
158
+ };
159
+ const reactive = (value) => {
160
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
161
+ return obj(value);
162
+ }
163
+ return new Reactive(value);
164
+ };
165
+ const tick = () => {
166
+ if (running) {
167
+ return;
168
+ }
169
+ running = true;
170
+ for (let i = 0, n = queue.length; i < n; i++) {
171
+ queue[i].get();
172
+ }
173
+ queue.length = 0;
174
+ running = false;
175
+ };
176
+ export { effect, reactive, tick };
package/build/obj.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ declare const factory: (values: Record<string, unknown>) => {};
2
+ export default factory;
package/build/obj.js ADDED
@@ -0,0 +1,25 @@
1
+ import { reactive } from './index';
2
+ const factory = (values) => {
3
+ let lazy = {}, properties = {};
4
+ for (let key in values) {
5
+ properties[key] = {
6
+ get() {
7
+ if (!lazy[key]) {
8
+ let value = values[key];
9
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
10
+ lazy[key] = factory(value);
11
+ }
12
+ else {
13
+ lazy[key] = reactive(value);
14
+ }
15
+ }
16
+ return lazy[key]?.get() || lazy[key];
17
+ },
18
+ set(value) {
19
+ lazy[key].set(value);
20
+ }
21
+ };
22
+ }
23
+ return Object.defineProperties({}, properties);
24
+ };
25
+ export default factory;
@@ -0,0 +1,4 @@
1
+ declare const CLEAN = 0;
2
+ declare const CHECK = 1;
3
+ declare const DIRTY = 2;
4
+ export { CLEAN, CHECK, DIRTY };
@@ -0,0 +1,4 @@
1
+ const CLEAN = 0;
2
+ const CHECK = 1;
3
+ const DIRTY = 2;
4
+ export { CLEAN, CHECK, DIRTY };
@@ -0,0 +1,3 @@
1
+ import { CLEAN, CHECK, DIRTY } from './symbols';
2
+ type State = typeof CHECK | typeof CLEAN | typeof DIRTY;
3
+ export { State };
package/build/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "author": "ICJR",
3
+ "description": "Reactivity",
4
+ "devDependencies": {
5
+ "tsc-alias": "^1.8.1",
6
+ "typescript": "^4.9.3"
7
+ },
8
+ "main": "./build/index.js",
9
+ "name": "@esportsplus/reactivity",
10
+ "private": false,
11
+ "scripts": {
12
+ "build": "tsc && tsc-alias",
13
+ "-": "-",
14
+ "prepare": "npm run build",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "types": "./build/index.d.ts",
18
+ "version": "0.0.1"
19
+ }
package/src/index.ts ADDED
@@ -0,0 +1,251 @@
1
+ import { CLEAN, CHECK, DIRTY } from './symbols';
2
+ import { State } from './types';
3
+ import obj from './obj';
4
+
5
+
6
+ let index = 0,
7
+ queue: Reactive<any>[] = [],
8
+ reaction: Reactive<any> | null = null,
9
+ running: boolean = false,
10
+ stack: Reactive<any>[] | null = null;
11
+
12
+
13
+ class Reactive<T> {
14
+ private effect: boolean;
15
+ private fn?: (onCleanup: (fn: VoidFunction) => void) => T;
16
+ private observers: Reactive<any>[] | null = null;
17
+ private sources: Reactive<any>[] | null = null;
18
+ private state: State;
19
+ private value: T;
20
+
21
+
22
+ cleanup: ((old: T) => void)[] | null = null;
23
+
24
+
25
+ constructor(_: ((fn: VoidFunction) => T) | T, effect: boolean = false) {
26
+ this.effect = effect;
27
+
28
+ if (typeof _ === 'function') {
29
+ this.fn = _ as (onCleanup: (fn: VoidFunction) => void) => T;
30
+ this.state = DIRTY;
31
+ this.value = undefined as any;
32
+
33
+ if (effect) {
34
+ this.update();
35
+ }
36
+ }
37
+ else {
38
+ this.state = CLEAN;
39
+ this.value = _;
40
+ }
41
+ }
42
+
43
+
44
+ get(): T {
45
+ if (reaction) {
46
+ if (!stack && reaction.sources && reaction.sources[index] == this) {
47
+ index++;
48
+ }
49
+ else {
50
+ if (!stack) {
51
+ stack = [this];
52
+ }
53
+ else {
54
+ stack.push(this);
55
+ }
56
+ }
57
+ }
58
+
59
+ if (this.fn) {
60
+ this.sync();
61
+ }
62
+
63
+ return this.value;
64
+ }
65
+
66
+ set(value: T): void {
67
+ if (this.observers && this.value !== value) {
68
+ for (let i = 0; i < this.observers.length; i++) {
69
+ this.observers[i].mark(DIRTY);
70
+ }
71
+ }
72
+
73
+ this.value = value;
74
+ }
75
+
76
+
77
+ private mark(state: typeof CHECK | typeof DIRTY): void {
78
+ if (this.state < state) {
79
+ // If previous state was clean we need to update effects
80
+ if (this.state === CLEAN && this.effect) {
81
+ queue.push(this);
82
+ }
83
+
84
+ this.state = state;
85
+
86
+ if (!this.observers) {
87
+ return;
88
+ }
89
+
90
+ for (let i = 0; i < this.observers.length; i++) {
91
+ this.observers[i].mark(CHECK);
92
+ }
93
+ }
94
+ }
95
+
96
+ // We don't actually delete sources here because we're replacing the entire array soon
97
+ private removeParentObservers(): void {
98
+ if (!this.sources) {
99
+ return;
100
+ }
101
+
102
+ for (let i = index; i < this.sources.length; i++) {
103
+ let source = this.sources[i];
104
+
105
+ source.observers![ source.observers!.findIndex((v) => v === this) ] = source.observers![source.observers!.length - 1];
106
+ source.observers!.pop();
107
+ }
108
+ }
109
+
110
+ // Update if dirty or if a parent is dirty
111
+ private sync(): void {
112
+ // If we are potentially dirty, see if we have a parent who has actually changed value
113
+ if (this.state === CHECK && this.sources) {
114
+ for (let i = 0, n = this.sources.length; i < n; i++) {
115
+ this.sources[i].sync();
116
+
117
+ // Stop the loop here so we won't trigger updates on other parents unnecessarily
118
+ // If our computation changes to no longer use some sources, we don't
119
+ // want to update() a source we used last time, but now don't use.
120
+ if ((this.state as State) === DIRTY) {
121
+ break;
122
+ }
123
+ }
124
+ }
125
+
126
+ // If we were already dirty or marked dirty by the step above, update.
127
+ if (this.state === DIRTY) {
128
+ this.update();
129
+ }
130
+
131
+ // By now, we're clean
132
+ this.state = CLEAN;
133
+ }
134
+
135
+ private update(): void {
136
+ let previous = {
137
+ index: index,
138
+ reaction: reaction,
139
+ stack: stack,
140
+ value: this.value
141
+ };
142
+
143
+ index = 0;
144
+ reaction = this;
145
+ stack = [];
146
+
147
+ try {
148
+ if (this.cleanup) {
149
+ for (let i = 0, n = this.cleanup.length; i < n; i++) {
150
+ this.cleanup[i]( this.value );
151
+ }
152
+
153
+ this.cleanup.length = 0;
154
+ }
155
+
156
+ this.value = this.fn!(
157
+ (fn: VoidFunction) => {
158
+ if (!this.cleanup) {
159
+ this.cleanup = [fn];
160
+ }
161
+ else {
162
+ this.cleanup.push(fn);
163
+ }
164
+ }
165
+ );
166
+
167
+ // If sources have changed, update source & observer links
168
+ if (stack.length) {
169
+ // Remove all old sources' observers links to us
170
+ this.removeParentObservers();
171
+
172
+ // Update source up links
173
+ if (this.sources && index > 0) {
174
+ this.sources.length = index + stack.length;
175
+
176
+ for (let i = 0; i < stack.length; i++) {
177
+ this.sources[index + i] = stack[i];
178
+ }
179
+ }
180
+ else {
181
+ this.sources = stack;
182
+ }
183
+
184
+ // Add ourselves to the end of the parent observers array
185
+ for (let i = index; i < this.sources.length; i++) {
186
+ let source = this.sources[i];
187
+
188
+ if (!source.observers) {
189
+ source.observers = [this];
190
+ }
191
+ else {
192
+ source.observers.push( this );
193
+ }
194
+ }
195
+ }
196
+ // Remove all old sources' observers links to us
197
+ else if (this.sources && index < this.sources.length) {
198
+ this.removeParentObservers();
199
+ this.sources.length = index;
200
+ }
201
+ }
202
+ finally {
203
+ index = previous.index;
204
+ reaction = previous.reaction;
205
+ stack = previous.stack;
206
+ }
207
+
208
+ // Handle diamond depenendencies if we're the parent of a diamond.
209
+ if (this.observers && previous.value !== this.value) {
210
+ for (let i = 0; i < this.observers.length; i++) {
211
+ this.observers[i].state = DIRTY;
212
+ }
213
+ }
214
+
215
+ this.state = CLEAN;
216
+ }
217
+ }
218
+
219
+
220
+
221
+ const effect = <T>(value: () => T) => {
222
+ return new Reactive(value, true);
223
+ };
224
+
225
+ const reactive = <T>(value: T) => {
226
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
227
+ return obj(value as Record<string, unknown>);
228
+ }
229
+
230
+ return new Reactive(value);
231
+ };
232
+
233
+ const tick = () => {
234
+ if (running) {
235
+ return;
236
+ }
237
+
238
+ running = true;
239
+
240
+ for (let i = 0, n = queue.length; i < n; i++) {
241
+ queue[i].get();
242
+ }
243
+
244
+ queue.length = 0;
245
+ running = false;
246
+ };
247
+
248
+
249
+ export { effect, reactive, tick };
250
+
251
+
package/src/obj.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { reactive } from './index';
2
+
3
+
4
+ const factory = (values: Record<string, unknown>) => {
5
+ let lazy: Record<string, any> = {},
6
+ properties: PropertyDescriptorMap = {};
7
+
8
+ for (let key in values) {
9
+ properties[key] = {
10
+ get() {
11
+ if (!lazy[key]) {
12
+ let value = values[key];
13
+
14
+ // if (Array.isArray(value)) {
15
+ // TODO: Need a solution
16
+ // }
17
+ // TODO: Can remove isArray once solution is found ^
18
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
19
+ lazy[key] = factory(value as Record<string, unknown>);
20
+ }
21
+ else {
22
+ lazy[key] = reactive(value);
23
+ }
24
+ }
25
+
26
+ return lazy[key]?.get() || lazy[key];
27
+ },
28
+ set(value: unknown) {
29
+ lazy[key].set(value);
30
+ }
31
+ };
32
+ }
33
+
34
+ return Object.defineProperties({}, properties);
35
+ };
36
+
37
+
38
+ export default factory;
package/src/symbols.ts ADDED
@@ -0,0 +1,8 @@
1
+ const CLEAN = 0;
2
+
3
+ const CHECK = 1;
4
+
5
+ const DIRTY = 2;
6
+
7
+
8
+ export { CLEAN, CHECK, DIRTY };
package/src/types.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { CLEAN, CHECK, DIRTY } from './symbols';
2
+
3
+
4
+ type State = typeof CHECK | typeof CLEAN | typeof DIRTY;
5
+
6
+
7
+ export { State };
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "alwaysStrict": true,
5
+ "baseUrl": "src",
6
+ "declaration": true,
7
+ "declarationDir": "./build",
8
+ "lib": ["dom", "esnext"],
9
+ "module": "esnext",
10
+ "moduleResolution": "nodenext",
11
+ "noUnusedLocals": true,
12
+ "noUnusedParameters": true,
13
+ "outDir": "./build",
14
+ "paths": {
15
+ "~/*": ["*"]
16
+ },
17
+ "removeComments": true,
18
+ "resolveJsonModule": true,
19
+ "strict": true,
20
+ "target": "esnext"
21
+ },
22
+ "exclude": ["node_modules"],
23
+ "include": ["src"]
24
+ }