@esportsplus/reactivity 0.0.5 → 0.0.7

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/build/index.d.ts CHANGED
@@ -1,29 +1,12 @@
1
- import { Queue } from './types';
2
- type Infer<T> = T extends (...args: any[]) => any ? Infer<ReturnType<T>> : T extends Record<string, any> ? {
3
- [K in keyof T]: Infer<T[K]>;
4
- } : Reactive<T>;
5
- declare class Reactive<T> {
6
- private effect;
7
- private fn?;
8
- private observers;
9
- private queue;
10
- private sources;
11
- private state;
12
- private value;
13
- cleanup: ((old: T) => void)[] | null;
14
- constructor(input: ((fn: VoidFunction) => T) | T, queue?: Queue | null, effect?: boolean);
15
- get(): T;
16
- set(value: T): void;
17
- private mark;
18
- private removeParentObservers;
19
- private sync;
20
- private update;
21
- }
22
- declare const effect: (queue: Queue) => <T>(value: () => T) => void;
23
- declare const reactive: <T>(value: T) => Infer<T>;
1
+ import { scheduler } from './reactive';
2
+ import { effect, reactive } from './methods';
24
3
  declare const _default: {
25
- effect: (queue: Queue) => <T>(value: () => T) => void;
26
- reactive: <T_1>(value: T_1) => Infer<T_1>;
4
+ effect: <T>(value: () => T) => void;
5
+ reactive: <T_1>(value: T_1) => import("./types").Infer<T_1>;
6
+ scheduler: {
7
+ add: (scheduler: import("./types").Scheduler) => void;
8
+ delete: (scheduler: import("./types").Scheduler) => void;
9
+ };
27
10
  };
28
11
  export default _default;
29
- export { effect, reactive };
12
+ export { effect, reactive, scheduler };
package/build/index.js CHANGED
@@ -1,179 +1,4 @@
1
- import { CLEAN, CHECK, DIRTY } from './symbols';
2
- import obj from './obj';
3
- let index = 0, reaction = null, stack = null;
4
- class Reactive {
5
- effect;
6
- fn;
7
- observers = null;
8
- queue = null;
9
- sources = null;
10
- state;
11
- value;
12
- cleanup = null;
13
- constructor(input, queue = null, effect = false) {
14
- this.effect = effect;
15
- if (typeof input === 'function') {
16
- this.fn = input;
17
- this.state = DIRTY;
18
- this.value = undefined;
19
- if (effect) {
20
- this.queue = queue;
21
- this.update();
22
- }
23
- }
24
- else {
25
- this.state = CLEAN;
26
- this.value = input;
27
- }
28
- }
29
- get() {
30
- if (reaction) {
31
- if (!stack && reaction.sources && reaction.sources[index] == this) {
32
- index++;
33
- }
34
- else {
35
- if (!stack) {
36
- stack = [this];
37
- }
38
- else {
39
- stack.push(this);
40
- }
41
- }
42
- }
43
- if (this.fn) {
44
- this.sync();
45
- }
46
- return this.value;
47
- }
48
- set(value) {
49
- if (this.observers && this.value !== value) {
50
- for (let i = 0; i < this.observers.length; i++) {
51
- this.observers[i].mark(DIRTY);
52
- }
53
- }
54
- this.value = value;
55
- }
56
- mark(state) {
57
- if (this.state < state) {
58
- if (this.state === CLEAN && this.effect) {
59
- if (!this.queue) {
60
- throw new Error('Effects cannot be updated without a queue');
61
- }
62
- this.queue.add(async () => {
63
- await this.get();
64
- });
65
- }
66
- this.state = state;
67
- if (!this.observers) {
68
- return;
69
- }
70
- for (let i = 0; i < this.observers.length; i++) {
71
- this.observers[i].mark(CHECK);
72
- }
73
- }
74
- }
75
- removeParentObservers() {
76
- if (!this.sources) {
77
- return;
78
- }
79
- for (let i = index; i < this.sources.length; i++) {
80
- let source = this.sources[i];
81
- source.observers[source.observers.findIndex((v) => v === this)] = source.observers[source.observers.length - 1];
82
- source.observers.pop();
83
- }
84
- }
85
- sync() {
86
- if (this.state === CHECK && this.sources) {
87
- for (let i = 0, n = this.sources.length; i < n; i++) {
88
- this.sources[i].sync();
89
- if (this.state === DIRTY) {
90
- break;
91
- }
92
- }
93
- }
94
- if (this.state === DIRTY) {
95
- this.update();
96
- }
97
- this.state = CLEAN;
98
- }
99
- update() {
100
- let previous = {
101
- index: index,
102
- reaction: reaction,
103
- stack: stack,
104
- value: this.value
105
- };
106
- index = 0;
107
- reaction = this;
108
- stack = [];
109
- try {
110
- if (this.cleanup) {
111
- for (let i = 0, n = this.cleanup.length; i < n; i++) {
112
- this.cleanup[i](this.value);
113
- }
114
- this.cleanup.length = 0;
115
- }
116
- this.value = this.fn((fn) => {
117
- if (!this.cleanup) {
118
- this.cleanup = [fn];
119
- }
120
- else {
121
- this.cleanup.push(fn);
122
- }
123
- });
124
- if (stack.length) {
125
- this.removeParentObservers();
126
- if (this.sources && index > 0) {
127
- this.sources.length = index + stack.length;
128
- for (let i = 0; i < stack.length; i++) {
129
- this.sources[index + i] = stack[i];
130
- }
131
- }
132
- else {
133
- this.sources = stack;
134
- }
135
- for (let i = index; i < this.sources.length; i++) {
136
- let source = this.sources[i];
137
- if (!source.observers) {
138
- source.observers = [this];
139
- }
140
- else {
141
- source.observers.push(this);
142
- }
143
- }
144
- }
145
- else if (this.sources && index < this.sources.length) {
146
- this.removeParentObservers();
147
- this.sources.length = index;
148
- }
149
- }
150
- finally {
151
- index = previous.index;
152
- reaction = previous.reaction;
153
- stack = previous.stack;
154
- }
155
- if (this.observers && previous.value !== this.value) {
156
- for (let i = 0; i < this.observers.length; i++) {
157
- this.observers[i].state = DIRTY;
158
- }
159
- }
160
- this.state = CLEAN;
161
- }
162
- }
163
- const effect = (queue) => {
164
- return (value) => {
165
- new Reactive(value, queue, true);
166
- };
167
- };
168
- const reactive = (value) => {
169
- let v;
170
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
171
- v = obj(value);
172
- }
173
- else {
174
- v = new Reactive(value);
175
- }
176
- return v;
177
- };
178
- export default { effect, reactive };
179
- export { effect, reactive };
1
+ import { scheduler } from './reactive';
2
+ import { effect, reactive } from './methods';
3
+ export default { effect, reactive, scheduler };
4
+ export { effect, reactive, scheduler };
@@ -0,0 +1,2 @@
1
+ declare const _default: <T>(value: () => T) => void;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import Reactive from '../reactive';
2
+ export default (value) => {
3
+ new Reactive(value, true);
4
+ };
@@ -0,0 +1,3 @@
1
+ import effect from './effect';
2
+ import reactive from './reactive';
3
+ export { effect, reactive };
@@ -0,0 +1,3 @@
1
+ import effect from './effect';
2
+ import reactive from './reactive';
3
+ export { effect, reactive };
@@ -0,0 +1,3 @@
1
+ import { Infer } from '../types';
2
+ declare const reactive: <T>(value: T) => Infer<T>;
3
+ export default reactive;
@@ -1,45 +1,39 @@
1
- import { reactive } from './index';
2
-
3
-
4
- function setup(value: unknown) {
5
- // if (Array.isArray(value)) {
6
- // TODO: Need a solution
7
- // }
8
- // TODO: Can remove isArray once solution is found ^
9
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
10
- return factory(value);
11
- }
12
-
13
- return reactive(value);
14
- }
15
-
16
-
17
- // TODO: Typecheck on values tro get rid of lazy var
18
- const factory = <T extends Record<string, any>>(values: T) => {
19
- let lazy: Record<string, any> = {},
20
- properties: PropertyDescriptorMap = {};
21
-
1
+ import Reactive from '../reactive';
2
+ function obj(values) {
3
+ let lazy = {}, properties = {};
22
4
  for (let key in values) {
23
5
  properties[key] = {
24
6
  get() {
25
7
  if (!lazy[key]) {
26
8
  lazy[key] = setup(values[key]);
27
9
  }
28
-
29
10
  return lazy[key].get();
30
11
  },
31
- set(value: unknown) {
12
+ set(value) {
32
13
  if (!lazy[key]) {
33
14
  lazy[key] = setup(values[key]);
34
15
  }
35
-
36
16
  lazy[key].set(value);
37
17
  }
38
18
  };
39
19
  }
40
-
41
20
  return Object.defineProperties({}, properties);
21
+ }
22
+ ;
23
+ function setup(value) {
24
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
25
+ return obj(value);
26
+ }
27
+ return reactive(value);
28
+ }
29
+ const reactive = (value) => {
30
+ let v;
31
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
32
+ v = obj(value);
33
+ }
34
+ else {
35
+ v = new Reactive(value);
36
+ }
37
+ return v;
42
38
  };
43
-
44
-
45
- export default factory;
39
+ export default reactive;
package/build/obj.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- declare const factory: <T extends Record<string, any>>(values: T) => {};
1
+ declare const factory: <T>(values: T) => T;
2
2
  export default factory;
@@ -0,0 +1,23 @@
1
+ import { Scheduler } from './types';
2
+ declare class Reactive<T> {
3
+ private effect;
4
+ private fn?;
5
+ private observers;
6
+ private sources;
7
+ private state;
8
+ private value;
9
+ cleanup: ((old: T) => void)[] | null;
10
+ constructor(input: ((fn: VoidFunction) => T) | T, effect?: boolean);
11
+ get(): T;
12
+ set(value: T): void;
13
+ private mark;
14
+ private removeParentObservers;
15
+ private sync;
16
+ private update;
17
+ }
18
+ declare const scheduler: {
19
+ add: (scheduler: Scheduler) => void;
20
+ delete: (scheduler: Scheduler) => void;
21
+ };
22
+ export default Reactive;
23
+ export { scheduler };
@@ -0,0 +1,175 @@
1
+ import { CLEAN, CHECK, DIRTY } from './symbols';
2
+ let index = 0, reaction = null, queue = [], schedulers = new Set, stack = null;
3
+ async function task() {
4
+ for (let i = 0, n = queue.length; i < n; i++) {
5
+ await queue[i].get();
6
+ }
7
+ queue.length = 0;
8
+ }
9
+ class Reactive {
10
+ effect;
11
+ fn;
12
+ observers = null;
13
+ sources = null;
14
+ state;
15
+ value;
16
+ cleanup = null;
17
+ constructor(input, effect = false) {
18
+ this.effect = effect;
19
+ if (typeof input === 'function') {
20
+ this.fn = input;
21
+ this.state = DIRTY;
22
+ this.value = undefined;
23
+ if (effect) {
24
+ this.update();
25
+ }
26
+ }
27
+ else {
28
+ this.state = CLEAN;
29
+ this.value = input;
30
+ }
31
+ }
32
+ get() {
33
+ if (reaction) {
34
+ if (!stack && reaction.sources && reaction.sources[index] == this) {
35
+ index++;
36
+ }
37
+ else {
38
+ if (!stack) {
39
+ stack = [this];
40
+ }
41
+ else {
42
+ stack.push(this);
43
+ }
44
+ }
45
+ }
46
+ if (this.fn) {
47
+ this.sync();
48
+ }
49
+ return this.value;
50
+ }
51
+ set(value) {
52
+ if (this.observers && this.value !== value) {
53
+ for (let i = 0; i < this.observers.length; i++) {
54
+ this.observers[i].mark(DIRTY);
55
+ }
56
+ }
57
+ this.value = value;
58
+ }
59
+ mark(state) {
60
+ if (this.state < state) {
61
+ if (this.effect && this.state === CLEAN) {
62
+ queue.push(this);
63
+ for (let scheduler of schedulers) {
64
+ scheduler.schedule();
65
+ }
66
+ }
67
+ this.state = state;
68
+ if (!this.observers) {
69
+ return;
70
+ }
71
+ for (let i = 0; i < this.observers.length; i++) {
72
+ this.observers[i].mark(CHECK);
73
+ }
74
+ }
75
+ }
76
+ removeParentObservers() {
77
+ if (!this.sources) {
78
+ return;
79
+ }
80
+ for (let i = index; i < this.sources.length; i++) {
81
+ let source = this.sources[i];
82
+ source.observers[source.observers.findIndex((v) => v === this)] = source.observers[source.observers.length - 1];
83
+ source.observers.pop();
84
+ }
85
+ }
86
+ sync() {
87
+ if (this.state === CHECK && this.sources) {
88
+ for (let i = 0, n = this.sources.length; i < n; i++) {
89
+ this.sources[i].sync();
90
+ if (this.state === DIRTY) {
91
+ break;
92
+ }
93
+ }
94
+ }
95
+ if (this.state === DIRTY) {
96
+ this.update();
97
+ }
98
+ this.state = CLEAN;
99
+ }
100
+ update() {
101
+ let previous = {
102
+ index: index,
103
+ reaction: reaction,
104
+ stack: stack,
105
+ value: this.value
106
+ };
107
+ index = 0;
108
+ reaction = this;
109
+ stack = [];
110
+ try {
111
+ if (this.cleanup) {
112
+ for (let i = 0, n = this.cleanup.length; i < n; i++) {
113
+ this.cleanup[i](this.value);
114
+ }
115
+ this.cleanup.length = 0;
116
+ }
117
+ this.value = this.fn((fn) => {
118
+ if (!this.cleanup) {
119
+ this.cleanup = [fn];
120
+ }
121
+ else {
122
+ this.cleanup.push(fn);
123
+ }
124
+ });
125
+ if (stack.length) {
126
+ this.removeParentObservers();
127
+ if (this.sources && index > 0) {
128
+ this.sources.length = index + stack.length;
129
+ for (let i = 0; i < stack.length; i++) {
130
+ this.sources[index + i] = stack[i];
131
+ }
132
+ }
133
+ else {
134
+ this.sources = stack;
135
+ }
136
+ for (let i = index; i < this.sources.length; i++) {
137
+ let source = this.sources[i];
138
+ if (!source.observers) {
139
+ source.observers = [this];
140
+ }
141
+ else {
142
+ source.observers.push(this);
143
+ }
144
+ }
145
+ }
146
+ else if (this.sources && index < this.sources.length) {
147
+ this.removeParentObservers();
148
+ this.sources.length = index;
149
+ }
150
+ }
151
+ finally {
152
+ index = previous.index;
153
+ reaction = previous.reaction;
154
+ stack = previous.stack;
155
+ }
156
+ if (this.observers && previous.value !== this.value) {
157
+ for (let i = 0; i < this.observers.length; i++) {
158
+ this.observers[i].state = DIRTY;
159
+ }
160
+ }
161
+ this.state = CLEAN;
162
+ }
163
+ }
164
+ const scheduler = {
165
+ add: (scheduler) => {
166
+ scheduler.tasks.add(task);
167
+ schedulers.add(scheduler);
168
+ },
169
+ delete: (scheduler) => {
170
+ scheduler.tasks.delete(task);
171
+ schedulers.delete(scheduler);
172
+ }
173
+ };
174
+ export default Reactive;
175
+ export { scheduler };
package/build/types.d.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  import { CLEAN, CHECK, DIRTY } from './symbols';
2
- type Queue = {
3
- add: (fn: () => Promise<void> | void) => void;
2
+ import Reactive from './reactive';
3
+ type Fn = () => Promise<unknown> | unknown;
4
+ type Infer<T> = T extends (...args: any[]) => any ? Reactive<T> : T extends Record<string, any> ? InferNested<T> : Reactive<T>;
5
+ type InferNested<T> = T extends (...args: any[]) => any ? InferNested<ReturnType<T>> : T extends Record<string, any> ? {
6
+ [K in keyof T]: InferNested<T[K]>;
7
+ } : T;
8
+ type Scheduler = {
9
+ schedule(): void;
10
+ tasks: {
11
+ add: (fn: Fn) => void;
12
+ delete: (fn: Fn) => void;
13
+ };
4
14
  };
5
15
  type State = typeof CHECK | typeof CLEAN | typeof DIRTY;
6
- export { Queue, State };
16
+ export { Infer, Scheduler, State };
package/package.json CHANGED
@@ -15,5 +15,5 @@
15
15
  "prepublishOnly": "npm run build"
16
16
  },
17
17
  "types": "./build/index.d.ts",
18
- "version": "0.0.5"
18
+ "version": "0.0.7"
19
19
  }
package/src/index.ts CHANGED
@@ -1,256 +1,6 @@
1
- import { CLEAN, CHECK, DIRTY } from './symbols';
2
- import { Queue, State } from './types';
3
- import obj from './obj';
1
+ import { scheduler } from './reactive';
2
+ import { effect, reactive } from './methods';
4
3
 
5
4
 
6
- type Infer<T> =
7
- T extends (...args: any[]) => any
8
- ? Infer<ReturnType<T>>
9
- : T extends Record<string, any>
10
- ? { [K in keyof T]: Infer<T[K]> }
11
- // Requires class type
12
- : Reactive<T>;
13
-
14
-
15
- let index = 0,
16
- reaction: Reactive<any> | null = null,
17
- stack: Reactive<any>[] | null = null;
18
-
19
-
20
- class Reactive<T> {
21
- private effect: boolean;
22
- private fn?: (onCleanup: (fn: VoidFunction) => void) => T;
23
- private observers: Reactive<any>[] | null = null;
24
- private queue: Queue | null = null;
25
- private sources: Reactive<any>[] | null = null;
26
- private state: State;
27
- private value: T;
28
-
29
-
30
- cleanup: ((old: T) => void)[] | null = null;
31
-
32
-
33
- constructor(input: ((fn: VoidFunction) => T) | T, queue: Queue | null = null, effect: boolean = false) {
34
- this.effect = effect;
35
-
36
- if (typeof input === 'function') {
37
- this.fn = input as (onCleanup: (fn: VoidFunction) => void) => T;
38
- this.state = DIRTY;
39
- this.value = undefined as any;
40
-
41
- if (effect) {
42
- this.queue = queue;
43
- this.update();
44
- }
45
- }
46
- else {
47
- this.state = CLEAN;
48
- this.value = input;
49
- }
50
- }
51
-
52
-
53
- get(): T {
54
- if (reaction) {
55
- if (!stack && reaction.sources && reaction.sources[index] == this) {
56
- index++;
57
- }
58
- else {
59
- if (!stack) {
60
- stack = [this];
61
- }
62
- else {
63
- stack.push(this);
64
- }
65
- }
66
- }
67
-
68
- if (this.fn) {
69
- this.sync();
70
- }
71
-
72
- return this.value;
73
- }
74
-
75
- set(value: T) {
76
- if (this.observers && this.value !== value) {
77
- for (let i = 0; i < this.observers.length; i++) {
78
- this.observers[i].mark(DIRTY);
79
- }
80
- }
81
-
82
- this.value = value;
83
- }
84
-
85
-
86
- private mark(state: typeof CHECK | typeof DIRTY) {
87
- if (this.state < state) {
88
- // If previous state was clean we need to update effects
89
- if (this.state === CLEAN && this.effect) {
90
- if (!this.queue) {
91
- throw new Error('Effects cannot be updated without a queue');
92
- }
93
-
94
- this.queue.add(async () => {
95
- await this.get();
96
- });
97
- }
98
-
99
- this.state = state;
100
-
101
- if (!this.observers) {
102
- return;
103
- }
104
-
105
- for (let i = 0; i < this.observers.length; i++) {
106
- this.observers[i].mark(CHECK);
107
- }
108
- }
109
- }
110
-
111
- // We don't actually delete sources here because we're replacing the entire array soon
112
- private removeParentObservers() {
113
- if (!this.sources) {
114
- return;
115
- }
116
-
117
- for (let i = index; i < this.sources.length; i++) {
118
- let source = this.sources[i];
119
-
120
- source.observers![ source.observers!.findIndex((v) => v === this) ] = source.observers![source.observers!.length - 1];
121
- source.observers!.pop();
122
- }
123
- }
124
-
125
- // Update if dirty or if a parent is dirty
126
- private sync() {
127
- // If we are potentially dirty, see if we have a parent who has actually changed value
128
- if (this.state === CHECK && this.sources) {
129
- for (let i = 0, n = this.sources.length; i < n; i++) {
130
- this.sources[i].sync();
131
-
132
- // Stop the loop here so we won't trigger updates on other parents unnecessarily
133
- // If our computation changes to no longer use some sources, we don't
134
- // want to update() a source we used last time, but now don't use.
135
- if ((this.state as State) === DIRTY) {
136
- break;
137
- }
138
- }
139
- }
140
-
141
- // If we were already dirty or marked dirty by the step above, update.
142
- if (this.state === DIRTY) {
143
- this.update();
144
- }
145
-
146
- // By now, we're clean
147
- this.state = CLEAN;
148
- }
149
-
150
- private update() {
151
- let previous = {
152
- index: index,
153
- reaction: reaction,
154
- stack: stack,
155
- value: this.value
156
- };
157
-
158
- index = 0;
159
- reaction = this;
160
- stack = [];
161
-
162
- try {
163
- if (this.cleanup) {
164
- for (let i = 0, n = this.cleanup.length; i < n; i++) {
165
- this.cleanup[i]( this.value );
166
- }
167
-
168
- this.cleanup.length = 0;
169
- }
170
-
171
- this.value = this.fn!(
172
- (fn: VoidFunction) => {
173
- if (!this.cleanup) {
174
- this.cleanup = [fn];
175
- }
176
- else {
177
- this.cleanup.push(fn);
178
- }
179
- }
180
- );
181
-
182
- // If sources have changed, update source & observer links
183
- if (stack.length) {
184
- // Remove all old sources' observers links to us
185
- this.removeParentObservers();
186
-
187
- // Update source up links
188
- if (this.sources && index > 0) {
189
- this.sources.length = index + stack.length;
190
-
191
- for (let i = 0; i < stack.length; i++) {
192
- this.sources[index + i] = stack[i];
193
- }
194
- }
195
- else {
196
- this.sources = stack;
197
- }
198
-
199
- // Add ourselves to the end of the parent observers array
200
- for (let i = index; i < this.sources.length; i++) {
201
- let source = this.sources[i];
202
-
203
- if (!source.observers) {
204
- source.observers = [this];
205
- }
206
- else {
207
- source.observers.push( this );
208
- }
209
- }
210
- }
211
- // Remove all old sources' observers links to us
212
- else if (this.sources && index < this.sources.length) {
213
- this.removeParentObservers();
214
- this.sources.length = index;
215
- }
216
- }
217
- finally {
218
- index = previous.index;
219
- reaction = previous.reaction;
220
- stack = previous.stack;
221
- }
222
-
223
- // Handle diamond depenendencies if we're the parent of a diamond.
224
- if (this.observers && previous.value !== this.value) {
225
- for (let i = 0; i < this.observers.length; i++) {
226
- this.observers[i].state = DIRTY;
227
- }
228
- }
229
-
230
- this.state = CLEAN;
231
- }
232
- }
233
-
234
-
235
- const effect = (queue: Queue) => {
236
- return <T>(value: () => T) => {
237
- new Reactive(value, queue, true);
238
- };
239
- };
240
-
241
- const reactive = <T>(value: T) => {
242
- let v;
243
-
244
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
245
- v = obj(value);
246
- }
247
- else {
248
- v = new Reactive(value);
249
- }
250
-
251
- return v as Infer<T>;
252
- };
253
-
254
-
255
- export default { effect, reactive };
256
- export { effect, reactive };
5
+ export default { effect, reactive, scheduler };
6
+ export { effect, reactive, scheduler };
@@ -0,0 +1,6 @@
1
+ import Reactive from '~/reactive';
2
+
3
+
4
+ export default <T>(value: () => T): void => {
5
+ new Reactive(value, true);
6
+ };
@@ -0,0 +1,5 @@
1
+ import effect from './effect';
2
+ import reactive from './reactive';
3
+
4
+
5
+ export { effect, reactive };
@@ -0,0 +1,59 @@
1
+ import { Infer } from '~/types';
2
+ import Reactive from '~/reactive';
3
+
4
+
5
+ // TODO: Typecheck on `values` to get rid of lazy var
6
+ function obj<T>(values: T) {
7
+ let lazy: Record<string, any> = {},
8
+ properties: PropertyDescriptorMap = {};
9
+
10
+ for (let key in values) {
11
+ properties[key] = {
12
+ get() {
13
+ if (!lazy[key]) {
14
+ lazy[key] = setup(values[key]);
15
+ }
16
+
17
+ return lazy[key].get();
18
+ },
19
+ set(value: unknown) {
20
+ if (!lazy[key]) {
21
+ lazy[key] = setup(values[key]);
22
+ }
23
+
24
+ lazy[key].set(value);
25
+ }
26
+ };
27
+ }
28
+
29
+ return Object.defineProperties({}, properties) as T;
30
+ };
31
+
32
+ function setup<T>(value: T) {
33
+ // if (Array.isArray(value)) {
34
+ // TODO
35
+ // }
36
+ // TODO: Can remove isArray implementation is created
37
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
38
+ return obj(value);
39
+ }
40
+
41
+ return reactive(value) as T;
42
+ }
43
+
44
+
45
+ const reactive = <T>(value: T) => {
46
+ let v: unknown;
47
+
48
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
49
+ v = obj(value);
50
+ }
51
+ else {
52
+ v = new Reactive(value);
53
+ }
54
+
55
+ return v as Infer<T>;
56
+ };
57
+
58
+
59
+ export default reactive;
@@ -0,0 +1,245 @@
1
+ import { CLEAN, CHECK, DIRTY } from './symbols';
2
+ import { Scheduler, State } from './types';
3
+
4
+
5
+ let index = 0,
6
+ reaction: Reactive<any> | null = null,
7
+ queue: Reactive<any>[] = [],
8
+ schedulers = new Set<Scheduler>,
9
+ stack: Reactive<any>[] | null = null;
10
+
11
+
12
+ async function task() {
13
+ for (let i = 0, n = queue.length; i < n; i++) {
14
+ await queue[i].get();
15
+ }
16
+
17
+ queue.length = 0;
18
+ }
19
+
20
+
21
+ class Reactive<T> {
22
+ private effect: boolean;
23
+ private fn?: (onCleanup: (fn: VoidFunction) => void) => T;
24
+ private observers: Reactive<any>[] | null = null;
25
+ private sources: Reactive<any>[] | null = null;
26
+ private state: State;
27
+ private value: T;
28
+
29
+
30
+ cleanup: ((old: T) => void)[] | null = null;
31
+
32
+
33
+ constructor(input: ((fn: VoidFunction) => T) | T, effect: boolean = false) {
34
+ this.effect = effect;
35
+
36
+ if (typeof input === 'function') {
37
+ this.fn = input as (onCleanup: (fn: VoidFunction) => void) => T;
38
+ this.state = DIRTY;
39
+ this.value = undefined as any;
40
+
41
+ if (effect) {
42
+ this.update();
43
+ }
44
+ }
45
+ else {
46
+ this.state = CLEAN;
47
+ this.value = input;
48
+ }
49
+ }
50
+
51
+
52
+ get(): T {
53
+ if (reaction) {
54
+ if (!stack && reaction.sources && reaction.sources[index] == this) {
55
+ index++;
56
+ }
57
+ else {
58
+ if (!stack) {
59
+ stack = [this];
60
+ }
61
+ else {
62
+ stack.push(this);
63
+ }
64
+ }
65
+ }
66
+
67
+ if (this.fn) {
68
+ this.sync();
69
+ }
70
+
71
+ return this.value;
72
+ }
73
+
74
+ set(value: T) {
75
+ if (this.observers && this.value !== value) {
76
+ for (let i = 0; i < this.observers.length; i++) {
77
+ this.observers[i].mark(DIRTY);
78
+ }
79
+ }
80
+
81
+ this.value = value;
82
+ }
83
+
84
+
85
+ private mark(state: typeof CHECK | typeof DIRTY) {
86
+ if (this.state < state) {
87
+ // If previous state was clean we need to update effects
88
+ if (this.effect && this.state === CLEAN) {
89
+ queue.push(this);
90
+
91
+ for (let scheduler of schedulers) {
92
+ scheduler.schedule();
93
+ }
94
+ }
95
+
96
+ this.state = state;
97
+
98
+ if (!this.observers) {
99
+ return;
100
+ }
101
+
102
+ for (let i = 0; i < this.observers.length; i++) {
103
+ this.observers[i].mark(CHECK);
104
+ }
105
+ }
106
+ }
107
+
108
+ // We don't actually delete sources here because we're replacing the entire array soon
109
+ private removeParentObservers() {
110
+ if (!this.sources) {
111
+ return;
112
+ }
113
+
114
+ for (let i = index; i < this.sources.length; i++) {
115
+ let source = this.sources[i];
116
+
117
+ source.observers![ source.observers!.findIndex((v) => v === this) ] = source.observers![source.observers!.length - 1];
118
+ source.observers!.pop();
119
+ }
120
+ }
121
+
122
+ // Update if dirty or if a parent is dirty
123
+ private sync() {
124
+ // If we are potentially dirty, see if we have a parent who has actually changed value
125
+ if (this.state === CHECK && this.sources) {
126
+ for (let i = 0, n = this.sources.length; i < n; i++) {
127
+ this.sources[i].sync();
128
+
129
+ // Stop the loop here so we won't trigger updates on other parents unnecessarily
130
+ // If our computation changes to no longer use some sources, we don't
131
+ // want to update() a source we used last time, but now don't use.
132
+ if ((this.state as State) === DIRTY) {
133
+ break;
134
+ }
135
+ }
136
+ }
137
+
138
+ // If we were already dirty or marked dirty by the step above, update.
139
+ if (this.state === DIRTY) {
140
+ this.update();
141
+ }
142
+
143
+ // By now, we're clean
144
+ this.state = CLEAN;
145
+ }
146
+
147
+ private update() {
148
+ let previous = {
149
+ index: index,
150
+ reaction: reaction,
151
+ stack: stack,
152
+ value: this.value
153
+ };
154
+
155
+ index = 0;
156
+ reaction = this;
157
+ stack = [];
158
+
159
+ try {
160
+ if (this.cleanup) {
161
+ for (let i = 0, n = this.cleanup.length; i < n; i++) {
162
+ this.cleanup[i]( this.value );
163
+ }
164
+
165
+ this.cleanup.length = 0;
166
+ }
167
+
168
+ this.value = this.fn!(
169
+ (fn: VoidFunction) => {
170
+ if (!this.cleanup) {
171
+ this.cleanup = [fn];
172
+ }
173
+ else {
174
+ this.cleanup.push(fn);
175
+ }
176
+ }
177
+ );
178
+
179
+ // If sources have changed, update source & observer links
180
+ if (stack.length) {
181
+ // Remove all old sources' observers links to us
182
+ this.removeParentObservers();
183
+
184
+ // Update source up links
185
+ if (this.sources && index > 0) {
186
+ this.sources.length = index + stack.length;
187
+
188
+ for (let i = 0; i < stack.length; i++) {
189
+ this.sources[index + i] = stack[i];
190
+ }
191
+ }
192
+ else {
193
+ this.sources = stack;
194
+ }
195
+
196
+ // Add ourselves to the end of the parent observers array
197
+ for (let i = index; i < this.sources.length; i++) {
198
+ let source = this.sources[i];
199
+
200
+ if (!source.observers) {
201
+ source.observers = [this];
202
+ }
203
+ else {
204
+ source.observers.push( this );
205
+ }
206
+ }
207
+ }
208
+ // Remove all old sources' observers links to us
209
+ else if (this.sources && index < this.sources.length) {
210
+ this.removeParentObservers();
211
+ this.sources.length = index;
212
+ }
213
+ }
214
+ finally {
215
+ index = previous.index;
216
+ reaction = previous.reaction;
217
+ stack = previous.stack;
218
+ }
219
+
220
+ // Handle diamond depenendencies if we're the parent of a diamond.
221
+ if (this.observers && previous.value !== this.value) {
222
+ for (let i = 0; i < this.observers.length; i++) {
223
+ this.observers[i].state = DIRTY;
224
+ }
225
+ }
226
+
227
+ this.state = CLEAN;
228
+ }
229
+ }
230
+
231
+
232
+ const scheduler = {
233
+ add: (scheduler: Scheduler) => {
234
+ scheduler.tasks.add(task);
235
+ schedulers.add(scheduler);
236
+ },
237
+ delete: (scheduler: Scheduler) => {
238
+ scheduler.tasks.delete(task);
239
+ schedulers.delete(scheduler);
240
+ }
241
+ };
242
+
243
+
244
+ export default Reactive;
245
+ export { scheduler };
package/src/types.ts CHANGED
@@ -1,11 +1,32 @@
1
1
  import { CLEAN, CHECK, DIRTY } from './symbols';
2
+ import Reactive from './reactive';
2
3
 
3
4
 
4
- type Queue = {
5
- add: (fn: () => Promise<void> | void) => void;
5
+ type Fn = () => Promise<unknown> | unknown;
6
+
7
+ type Infer<T> =
8
+ T extends (...args: any[]) => any
9
+ ? Reactive<T>
10
+ : T extends Record<string, any>
11
+ ? InferNested<T>
12
+ : Reactive<T>;
13
+
14
+ type InferNested<T> =
15
+ T extends (...args: any[]) => any
16
+ ? InferNested< ReturnType<T> >
17
+ : T extends Record<string, any>
18
+ ? { [K in keyof T]: InferNested<T[K]> }
19
+ : T;
20
+
21
+ type Scheduler = {
22
+ schedule(): void;
23
+ tasks: {
24
+ add: (fn: Fn) => void;
25
+ delete: (fn: Fn) => void;
26
+ }
6
27
  };
7
28
 
8
29
  type State = typeof CHECK | typeof CLEAN | typeof DIRTY;
9
30
 
10
31
 
11
- export { Queue, State };
32
+ export { Infer, Scheduler, State };