@esportsplus/reactivity 0.16.7 → 0.17.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/build/system.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isArray, isObject } from '@esportsplus/utilities';
1
+ import { isObject } from '@esportsplus/utilities';
2
2
  import { COMPUTED, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_NOTIFY_MASK, STATE_RECOMPUTING } from './constants.js';
3
3
  let depth = 0, heap = new Array(64), heap_i = 0, heap_n = 0, microtask = queueMicrotask, notified = false, observer = null, scope = null, stabilizer = STABILIZER_IDLE, version = 0;
4
4
  function cleanup(computed) {
@@ -6,14 +6,14 @@ function cleanup(computed) {
6
6
  return;
7
7
  }
8
8
  let value = computed.cleanup;
9
- if (isArray(value)) {
9
+ if (typeof value === 'function') {
10
+ value();
11
+ }
12
+ else {
10
13
  for (let i = 0, n = value.length; i < n; i++) {
11
14
  value[i]();
12
15
  }
13
16
  }
14
- else {
15
- value();
16
- }
17
17
  computed.cleanup = null;
18
18
  }
19
19
  function deleteFromHeap(computed) {
@@ -64,20 +64,20 @@ function insertIntoHeap(computed) {
64
64
  }
65
65
  function link(dep, sub) {
66
66
  let prevDep = sub.depsTail;
67
- if (prevDep !== null && prevDep.dep === dep) {
67
+ if (prevDep && prevDep.dep === dep) {
68
68
  return;
69
69
  }
70
70
  let nextDep = null;
71
71
  if (sub.state & STATE_RECOMPUTING) {
72
- nextDep = prevDep !== null ? prevDep.nextDep : sub.deps;
73
- if (nextDep !== null && nextDep.dep === dep) {
72
+ nextDep = prevDep ? prevDep.nextDep : sub.deps;
73
+ if (nextDep && nextDep.dep === dep) {
74
74
  nextDep.version = version;
75
75
  sub.depsTail = nextDep;
76
76
  return;
77
77
  }
78
78
  }
79
79
  let prevSub = dep.subsTail;
80
- if (prevSub !== null &&
80
+ if (prevSub &&
81
81
  prevSub.version === version &&
82
82
  prevSub.sub === sub) {
83
83
  return;
@@ -91,13 +91,13 @@ function link(dep, sub) {
91
91
  nextSub: null,
92
92
  version
93
93
  };
94
- if (prevDep !== null) {
94
+ if (prevDep) {
95
95
  prevDep.nextDep = newLink;
96
96
  }
97
97
  else {
98
98
  sub.deps = newLink;
99
99
  }
100
- if (prevSub !== null) {
100
+ if (prevSub) {
101
101
  prevSub.nextSub = newLink;
102
102
  }
103
103
  else {
@@ -110,7 +110,7 @@ function notify(computed, newState = STATE_DIRTY) {
110
110
  return;
111
111
  }
112
112
  computed.state = state | newState;
113
- for (let link = computed.subs; link !== null; link = link.nextSub) {
113
+ for (let link = computed.subs; link; link = link.nextSub) {
114
114
  notify(link.sub, STATE_CHECK);
115
115
  }
116
116
  }
@@ -140,12 +140,12 @@ function recompute(computed, del) {
140
140
  depth--;
141
141
  observer = o;
142
142
  computed.state = STATE_NONE;
143
- let depsTail = computed.depsTail, remove = depsTail !== null ? depsTail.nextDep : computed.deps;
144
- if (remove !== null) {
143
+ let depsTail = computed.depsTail, remove = depsTail ? depsTail.nextDep : computed.deps;
144
+ if (remove) {
145
145
  do {
146
146
  remove = unlink(remove);
147
- } while (remove !== null);
148
- if (depsTail !== null) {
147
+ } while (remove);
148
+ if (depsTail) {
149
149
  depsTail.nextDep = null;
150
150
  }
151
151
  else {
@@ -154,7 +154,7 @@ function recompute(computed, del) {
154
154
  }
155
155
  if (ok && value !== computed.value) {
156
156
  computed.value = value;
157
- for (let c = computed.subs; c !== null; c = c.nextSub) {
157
+ for (let c = computed.subs; c; c = c.nextSub) {
158
158
  let s = c.sub, state = s.state;
159
159
  if (state & STATE_CHECK) {
160
160
  s.state = state | STATE_DIRTY;
@@ -165,6 +165,9 @@ function recompute(computed, del) {
165
165
  }
166
166
  }
167
167
  function schedule() {
168
+ if (stabilizer === STABILIZER_SCHEDULED) {
169
+ return;
170
+ }
168
171
  if (stabilizer === STABILIZER_IDLE && !depth) {
169
172
  stabilizer = STABILIZER_SCHEDULED;
170
173
  microtask(stabilize);
@@ -199,13 +202,13 @@ function stabilize() {
199
202
  }
200
203
  function unlink(link) {
201
204
  let dep = link.dep, nextDep = link.nextDep, nextSub = link.nextSub, prevSub = link.prevSub;
202
- if (nextSub !== null) {
205
+ if (nextSub) {
203
206
  nextSub.prevSub = prevSub;
204
207
  }
205
208
  else {
206
209
  dep.subsTail = prevSub;
207
210
  }
208
- if (prevSub !== null) {
211
+ if (prevSub) {
209
212
  prevSub.nextSub = nextSub;
210
213
  }
211
214
  else if ((dep.subs = nextSub) === null && 'fn' in dep) {
@@ -271,7 +274,7 @@ const computed = (fn) => {
271
274
  const dispose = (computed) => {
272
275
  deleteFromHeap(computed);
273
276
  let dep = computed.deps;
274
- while (dep !== null) {
277
+ while (dep) {
275
278
  dep = unlink(dep);
276
279
  }
277
280
  computed.deps = null;
@@ -300,11 +303,11 @@ const onCleanup = (fn) => {
300
303
  if (!cleanup) {
301
304
  parent.cleanup = fn;
302
305
  }
303
- else if (isArray(cleanup)) {
304
- cleanup.push(fn);
306
+ else if (typeof cleanup === 'function') {
307
+ parent.cleanup = [cleanup, fn];
305
308
  }
306
309
  else {
307
- parent.cleanup = [cleanup, fn];
310
+ cleanup.push(fn);
308
311
  }
309
312
  return fn;
310
313
  };
@@ -361,7 +364,7 @@ const set = (signal, value) => {
361
364
  if (signal.subs === null) {
362
365
  return;
363
366
  }
364
- for (let link = signal.subs; link !== null; link = link.nextSub) {
367
+ for (let link = signal.subs; link; link = link.nextSub) {
365
368
  insertIntoHeap(link.sub);
366
369
  }
367
370
  schedule();
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "private": false,
13
13
  "type": "module",
14
14
  "types": "build/index.d.ts",
15
- "version": "0.16.7",
15
+ "version": "0.17.0",
16
16
  "scripts": {
17
17
  "build": "tsc && tsc-alias",
18
18
  "-": "-"
@@ -1,19 +1,7 @@
1
- import { isNumber, Prettify } from '@esportsplus/utilities';
2
1
  import { REACTIVE_ARRAY } from '~/constants';
3
2
  import { isReactiveObject } from './object';
4
3
 
5
4
 
6
- type ReactiveArray<T> = Prettify<
7
- T[] & {
8
- clear: () => void;
9
- dispose: () => void;
10
- dispatch: <K extends keyof Events<T>, V>(event: K, value?: V) => void;
11
- map: <R>(fn: (this: ReactiveArray<T>, value: T, i: number) => R) => R[];
12
- on: <K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) => void;
13
- once: <K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) => void;
14
- }
15
- >;
16
-
17
5
  type Events<T> = {
18
6
  clear: undefined,
19
7
  pop: {
@@ -49,243 +37,161 @@ type Listener<V> = {
49
37
  type Listeners = Record<string, (Listener<any> | null)[]>;
50
38
 
51
39
 
52
- function cleanup<T>(item: T) {
53
- if (isReactiveObject(item)) {
54
- item.dispose();
55
- }
56
- }
40
+ class ReactiveArray<T> extends Array<T> {
41
+ [REACTIVE_ARRAY] = true;
42
+ listeners: Listeners = {};
57
43
 
58
- function clear<T>(data: T[], listeners: Listeners) {
59
- dispose(data);
60
- dispatch(listeners, 'clear');
61
- }
62
44
 
63
- function dispatch<T, K extends keyof Events<T>, V>(listeners: Listeners, event: K, value?: V) {
64
- if (listeners === null || listeners[event] === undefined) {
65
- return;
45
+ constructor(...items: T[]) {
46
+ super(...items);
66
47
  }
67
48
 
68
- let bucket = listeners[event];
69
49
 
70
- for (let i = 0, n = bucket.length; i < n; i++) {
71
- let listener = bucket[i];
50
+ clear() {
51
+ this.dispose();
52
+ this.dispatch('clear');
53
+ }
54
+
55
+ dispatch<K extends keyof Events<T>, V>(event: K, value?: V) {
56
+ let listeners = this.listeners[event];
72
57
 
73
- if (listener === null) {
74
- continue;
58
+ if (!listeners) {
59
+ return;
75
60
  }
76
61
 
77
- try {
78
- listener(value);
62
+ for (let i = 0, n = listeners.length; i < n; i++) {
63
+ let listener = listeners[i];
79
64
 
80
- if (listener.once !== undefined) {
81
- bucket[i] = null;
65
+ if (listener === null) {
66
+ continue;
67
+ }
68
+
69
+ try {
70
+ listener(value);
71
+
72
+ if (listener.once !== undefined) {
73
+ listeners[i] = null;
74
+ }
75
+ }
76
+ catch {
77
+ listeners[i] = null;
82
78
  }
83
- }
84
- catch {
85
- bucket[i] = null;
86
79
  }
87
80
  }
88
- }
89
81
 
90
- function dispose<T>(data: T[]) {
91
- let item;
82
+ dispose() {
83
+ let item;
92
84
 
93
- while (item = data.pop()) {
94
- cleanup(item);
85
+ while (item = super.pop()) {
86
+ if (isReactiveObject(item)) {
87
+ item.dispose();
88
+ }
89
+ }
95
90
  }
96
- }
97
91
 
98
- function map<T, R>(
99
- data: T[],
100
- proxy: ReactiveArray<T>,
101
- fn: (this: ReactiveArray<T>, value: T, i: number) => R
102
- ) {
103
- let n = data.length,
104
- values: R[] = new Array(n);
92
+ on<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) {
93
+ let listeners = this.listeners[event];
105
94
 
106
- for (let i = 0; i < n; i++) {
107
- values[i] = fn.call(proxy, data[i], i);
108
- }
95
+ if (listeners === undefined) {
96
+ this.listeners[event] = [listener];
97
+ }
98
+ else {
99
+ let hole = listeners.length;
109
100
 
110
- return values;
111
- }
101
+ for (let i = 0, n = hole; i < n; i++) {
102
+ let l = listeners[i];
112
103
 
113
- function on<T, K extends keyof Events<T>>(listeners: Listeners, event: K, listener: Listener<Events<T>[K]>) {
114
- let bucket = listeners[event];
104
+ if (l === listener) {
105
+ return;
106
+ }
107
+ else if (l === null && hole === n) {
108
+ hole = i;
109
+ }
110
+ }
111
+
112
+ listeners[hole] = listener;
113
+ }
114
+ }
115
115
 
116
- if (bucket === undefined) {
117
- listeners[event] = [listener];
116
+ once<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) {
117
+ listener.once = true;
118
+ this.on(event, listener);
118
119
  }
119
- else {
120
- let hole = bucket.length;
121
120
 
122
- for (let i = 0, n = hole; i < n; i++) {
123
- let l = bucket[i];
121
+ pop() {
122
+ let item = super.pop();
124
123
 
125
- if (l === listener) {
126
- return;
127
- }
128
- else if (l === null && hole === n) {
129
- hole = i;
124
+ if (item !== undefined) {
125
+ if (isReactiveObject(item)) {
126
+ item.dispose();
130
127
  }
128
+ this.dispatch('pop', { item });
131
129
  }
132
130
 
133
- bucket[hole] = listener;
131
+ return item;
134
132
  }
135
- }
136
133
 
137
- function once<T, K extends keyof Events<T>>(listeners: Listeners, event: K, listener: Listener<Events<T>[K]>) {
138
- listener.once = true;
139
- on(listeners, event, listener);
140
- }
134
+ push(...items: T[]) {
135
+ let length = super.push(...items);
141
136
 
142
- function pop<T>(data: T[], listeners: Listeners) {
143
- let item = data.pop();
137
+ this.dispatch('push', { items });
144
138
 
145
- if (item !== undefined) {
146
- cleanup(item);
147
- dispatch(listeners, 'pop', { item });
139
+ return length;
148
140
  }
149
141
 
150
- return item;
151
- }
142
+ reverse() {
143
+ super.reverse();
144
+ this.dispatch('reverse');
152
145
 
153
- function push<T>(data: T[], listeners: Listeners, items: T[]) {
154
- let n = data.push(...items);
146
+ return this;
147
+ }
155
148
 
156
- dispatch(listeners, 'push', { items });
149
+ shift() {
150
+ let item = super.shift();
157
151
 
158
- return n;
159
- }
152
+ if (item !== undefined) {
153
+ if (isReactiveObject(item)) {
154
+ item.dispose();
155
+ }
156
+ this.dispatch('shift', { item });
157
+ }
160
158
 
161
- function reverse<T>(data: T[], listeners: Listeners) {
162
- data.reverse();
163
- dispatch(listeners, 'reverse');
164
- }
159
+ return item;
160
+ }
165
161
 
166
- function shift<T>(data: T[], listeners: Listeners) {
167
- let item = data.shift();
162
+ sort(fn: (a: T, b: T) => number) {
163
+ super.sort(fn);
164
+ this.dispatch('sort');
168
165
 
169
- if (item !== undefined) {
170
- cleanup(item);
171
- dispatch(listeners, 'shift', { item });
166
+ return this;
172
167
  }
173
168
 
174
- return item;
175
- }
169
+ splice(start: number, deleteCount: number = this.length, ...items: T[]) {
170
+ let removed = super.splice(start, deleteCount, ...items);
176
171
 
177
- function sort<T>(data: T[], listeners: Listeners, fn: (a: T, b: T) => number) {
178
- data.sort((a, b) => fn(a, b));
179
- dispatch(listeners, 'sort');
180
- }
172
+ if (items.length > 0 || removed.length > 0) {
173
+ for (let i = 0, n = removed.length; i < n; i++) {
174
+ let item = removed[i];
181
175
 
182
- function splice<T>(data: T[], listeners: Listeners, start: number, deleteCount: number = data.length, items: T[] = []) {
183
- let removed = data.splice(start, deleteCount, ...items);
176
+ if (isReactiveObject(item)) {
177
+ item.dispose();
178
+ }
179
+ }
184
180
 
185
- if (items.length > 0 || removed.length > 0) {
186
- for (let i = 0, n = removed.length; i < n; i++) {
187
- cleanup(removed[i]);
181
+ this.dispatch('splice', { deleteCount, items, start });
188
182
  }
189
183
 
190
- dispatch(listeners, 'splice', {
191
- deleteCount,
192
- items,
193
- start
194
- });
184
+ return removed;
195
185
  }
196
186
 
197
- return removed;
198
- }
199
-
200
- function unshift<T>(data: T[], listeners: Listeners, items: T[]) {
201
- let length = data.unshift(...items);
187
+ unshift(...items: T[]) {
188
+ let length = super.unshift(...items);
202
189
 
203
- dispatch(listeners, 'unshift', { items });
190
+ this.dispatch('unshift', { items });
204
191
 
205
- return length;
192
+ return length;
193
+ }
206
194
  }
207
195
 
208
196
 
209
- export default <T>(data: T[]) => {
210
- let listeners: Listeners = {},
211
- proxy = new Proxy({}, {
212
- get(_, key: any) {
213
- if (isNumber(key)) {
214
- return data[key];
215
- }
216
- else if (key in wrapper) {
217
- return wrapper[key as keyof typeof wrapper];
218
- }
219
- else if (key === 'length') {
220
- return data.length;
221
- }
222
-
223
- return data[key];
224
- },
225
- set(_, key: any, value: any) {
226
- if (isNumber(key)) {
227
- splice(data, listeners, key, 1, value);
228
- }
229
- else if (key === 'length') {
230
- if (value >= data.length) {
231
- }
232
- else if (value === 0) {
233
- clear(data, listeners);
234
- }
235
- else {
236
- splice(data, listeners, value);
237
- }
238
- }
239
- else {
240
- return false;
241
- }
242
-
243
- return true;
244
- }
245
- }) as ReactiveArray<T>,
246
- wrapper = {
247
- [REACTIVE_ARRAY]: true,
248
- at: (i: number) => data[i],
249
- clear: () => {
250
- clear(data, listeners);
251
- return proxy;
252
- },
253
- dispatch: <K extends keyof Events<T>, V>(event: K, value?: V) => {
254
- dispatch(listeners, event, value);
255
- return proxy;
256
- },
257
- dispose: () => {
258
- dispose(data);
259
- return proxy;
260
- },
261
- map: <R>(fn: (this: ReactiveArray<T>, value: T, i: number) => R) => {
262
- return map(data, proxy, fn);
263
- },
264
- on: <K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) => {
265
- on(listeners, event, listener);
266
- return proxy;
267
- },
268
- once: <K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) => {
269
- once(listeners, event, listener);
270
- return proxy;
271
- },
272
- pop: () => pop(data, listeners),
273
- push: (...items: T[]) => push(data, listeners, items),
274
- reverse: () => {
275
- reverse(data, listeners);
276
- return proxy;
277
- },
278
- shift: () => shift(data, listeners),
279
- sort: (fn: (a: T, b: T) => number) => {
280
- sort(data, listeners, fn);
281
- return proxy;
282
- },
283
- splice: (start: number, deleteCount?: number, ...items: T[]) => {
284
- return splice(data, listeners, start, deleteCount, items);
285
- },
286
- unshift: (...items: T[]) => unshift(data, listeners, items),
287
- };
288
-
289
- return proxy;
290
- };
291
- export type { ReactiveArray };
197
+ export { ReactiveArray };
@@ -1,29 +1,42 @@
1
- import { isArray, isObject } from '@esportsplus/utilities';
1
+ import { isArray, isObject, Prettify } from '@esportsplus/utilities';
2
2
  import { onCleanup, root } from '~/system';
3
- import array, { ReactiveArray } from './array';
4
- import object, { ReactiveObject } from './object';
3
+ import { ReactiveArray } from './array';
4
+ import { ReactiveObject } from './object';
5
5
 
6
6
 
7
7
  type API<T> =
8
8
  T extends Record<PropertyKey, unknown>
9
- ? ReactiveObject<T>
9
+ ? Prettify<{ [K in keyof T]: Infer<T[K]> } & { dispose: VoidFunction } >
10
10
  : T extends (infer U)[]
11
11
  ? ReactiveArray<U>
12
12
  : never;
13
13
 
14
+ type Guard<T> = T extends { dispose: any } ? { never: '[ dispose ] are reserved keys' } : T;
14
15
 
15
- export default <T extends Record<PropertyKey, any> | unknown[]>(
16
- input: T extends { dispose: any } ? { never: '[ dispose ] are reserved keys' } : T
17
- ): API<T> => {
16
+ type Infer<T> =
17
+ T extends (...args: unknown[]) => Promise<infer R>
18
+ ? R | undefined
19
+ : T extends (...args: any[]) => infer R
20
+ ? R
21
+ : T extends (infer U)[]
22
+ ? ReactiveArray<U>
23
+ : T extends ReactiveObject<any>
24
+ ? T
25
+ : T extends Record<PropertyKey, unknown>
26
+ ? { [K in keyof T]: T[K] }
27
+ : T;
28
+
29
+
30
+ export default <T extends Record<PropertyKey, any> | unknown[]>(input: Guard<T>): API<T> => {
18
31
  let dispose = false,
19
32
  value = root(() => {
20
33
  let response: API<T> | undefined;
21
34
 
22
- if (isArray(input)) {
23
- response = array(input) as API<T>;
35
+ if (isObject(input)) {
36
+ response = new ReactiveObject(input) as any as API<T>;
24
37
  }
25
- else if (isObject(input)) {
26
- response = object(input) as API<T>;
38
+ else if (isArray(input)) {
39
+ response = new ReactiveArray(...input) as API<T>;
27
40
  }
28
41
 
29
42
  if (response) {