@esportsplus/reactivity 0.4.8 → 0.6.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/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
- "@esportsplus/custom-function": "^0.0.12",
5
- "@esportsplus/utilities": "^0.19.0"
4
+ "@esportsplus/custom-function": "^0.0.13",
5
+ "@esportsplus/utilities": "^0.19.1"
6
6
  },
7
7
  "devDependencies": {
8
- "@esportsplus/typescript": "^0.9.1"
8
+ "@esportsplus/typescript": "^0.9.2"
9
9
  },
10
10
  "main": "build/index.js",
11
11
  "name": "@esportsplus/reactivity",
12
12
  "private": false,
13
13
  "type": "module",
14
14
  "types": "build/index.d.ts",
15
- "version": "0.4.8",
15
+ "version": "0.6.0",
16
16
  "scripts": {
17
17
  "build": "tsc && tsc-alias",
18
18
  "-": "-"
package/readme.md CHANGED
@@ -1,2 +1,2 @@
1
- https://github.com/maverick-js/signals
2
- https://github.com/modderme123/reactively
1
+ https://github.com/milomg/r3
2
+ https://github.com/stackblitz/alien-signals
package/src/constants.ts CHANGED
@@ -1,19 +1,14 @@
1
- const CLEAN = 0;
1
+ const REACTIVE = Symbol('reactive');
2
2
 
3
- const CHECK = 1;
3
+ const STATE_NONE = 0;
4
4
 
5
- const DIRTY = 2;
5
+ const STATE_CHECK = 1 << 0;
6
6
 
7
- const DISPOSED = 3;
7
+ const STATE_DIRTY = 1 << 1;
8
8
 
9
+ const STATE_RECOMPUTING = 1 << 2;
9
10
 
10
- const COMPUTED = 0;
11
+ const STATE_IN_HEAP = 1 << 3;
11
12
 
12
- const EFFECT = 1;
13
13
 
14
- const ROOT = 2;
15
-
16
- const SIGNAL = 3;
17
-
18
-
19
- export { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, ROOT, SIGNAL };
14
+ export { REACTIVE, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
package/src/index.ts CHANGED
@@ -1,5 +1,3 @@
1
- export { default as macro } from './macro';
2
1
  export { default as reactive } from './reactive';
3
- export { computed, dispose, effect, root, signal } from './signal';
4
- export * from './constants';
2
+ export { computed, dispose, oncleanup, root, signal, stabilize } from './signal';
5
3
  export * from './types';
@@ -1,8 +1,8 @@
1
- import { Infer } from '~/types';
2
- import { isInstanceOf, isNumber, isObject } from '@esportsplus/utilities';
3
- import { dispose, signal, Reactive } from '~/signal';
4
- import { Listener, Options, ReactiveObject, Signal } from '~/types';
5
- import object from './object';
1
+ import { isArray, isFunction, isInstanceOf, isNumber, isObject } from '@esportsplus/utilities';
2
+ import { computed, dispose, isComputed, read } from '~/signal';
3
+ import { Computed, Infer } from '~/types';
4
+ import object, { ReactiveObject } from './object';
5
+ import { Disposable } from './disposable';
6
6
 
7
7
 
8
8
  type API<T> = Infer<T>[] & ReactiveArray<T>;
@@ -33,21 +33,32 @@ type Events<T> = {
33
33
  };
34
34
  };
35
35
 
36
- type Item<T> = T extends Record<PropertyKey, unknown> ? ReactiveObject<T> : Signal<T>;
36
+ type Item<T> = Computed<T> | ReactiveArray<T> | ReactiveObject<T extends Record<PropertyKey, unknown> ? T : never> | T;
37
+
38
+ type Listener<V> = {
39
+ once?: boolean;
40
+ (value: V): void;
41
+ };
42
+
43
+ type Value<T> =
44
+ T extends Record<PropertyKey, unknown>
45
+ ? ReactiveObject<T>
46
+ : T extends Array<infer U>
47
+ ? ReactiveArray<U>
48
+ : T;
37
49
 
38
50
 
39
- class ReactiveArray<T> {
40
- private data: Item<T>[]
41
- private options: Options;
51
+ class ReactiveArray<T> extends Disposable {
52
+ private data: Item<T>[];
53
+ private listeners: Record<string, (Listener<any> | null)[]> | null = null;
42
54
  private proxy: API<T>;
43
- private signal: Signal<boolean>;
44
55
 
45
56
 
46
- constructor(data: Item<T>[], proxy: API<T>, options: Options = {}) {
57
+ constructor(data: Item<T>[], proxy: API<T>) {
58
+ super();
59
+
47
60
  this.data = data;
48
- this.options = options;
49
61
  this.proxy = proxy;
50
- this.signal = signal(false);
51
62
  }
52
63
 
53
64
 
@@ -67,37 +78,64 @@ class ReactiveArray<T> {
67
78
  at(i: number) {
68
79
  let value = this.data[i];
69
80
 
70
- if (isInstanceOf(value, Reactive)) {
71
- return value.get();
81
+ if (isComputed(value)) {
82
+ return read(value);
72
83
  }
73
84
 
74
85
  return value;
75
86
  }
76
87
 
77
- dispatch<E extends keyof Events<unknown>>(event: E, data?: Events<T>[E]) {
78
- this.signal.dispatch(event, data);
79
- }
88
+ dispatch<D>(event: keyof Events<T>, value?: D) {
89
+ if (this.listeners === null || this.listeners[event] === undefined) {
90
+ return;
91
+ }
80
92
 
81
- dispose() {
82
- this.signal.dispose();
83
- dispose(this.data);
93
+ let listeners = this.listeners[event];
94
+
95
+ for (let i = 0, n = listeners.length; i < n; i++) {
96
+ let listener = listeners[i];
97
+
98
+ if (listener === null) {
99
+ continue;
100
+ }
101
+
102
+ try {
103
+ listener(value);
104
+
105
+ if (listener.once !== undefined) {
106
+ listeners[i] = null;
107
+ }
108
+ }
109
+ catch {
110
+ listeners[i] = null;
111
+ }
112
+ }
84
113
  }
85
114
 
86
- indexOf(value: T, fromIndex?: number) {
115
+ dispose() {
87
116
  let data = this.data;
88
117
 
89
- for (let i = fromIndex ?? 0, n = data.length; i < n; i++) {
90
- if (data[i].value === value) {
91
- return i;
118
+ for (let i = 0, n = data.length; i < n; i++) {
119
+ let value = data[i];
120
+
121
+ if (isInstanceOf(value, Disposable)) {
122
+ value.dispose();
123
+ }
124
+ else if (isComputed(value)) {
125
+ dispose(value);
92
126
  }
93
127
  }
94
128
 
95
- return -1;
129
+ this.listeners = null;
96
130
  }
97
131
 
98
- map<U>(fn: (this: API<T>, value: T, i: number) => U, i?: number, n?: number) {
132
+ map<R>(
133
+ fn: (this: API<T>, value: Value<T>, i: number) => R,
134
+ i?: number,
135
+ n?: number
136
+ ) {
99
137
  let { data, proxy } = this,
100
- values: U[] = [];
138
+ values: R[] = [];
101
139
 
102
140
  if (i === undefined) {
103
141
  i = 0;
@@ -113,44 +151,71 @@ class ReactiveArray<T> {
113
151
  let item = data[i];
114
152
 
115
153
  values.push(
116
- fn.call(proxy, isInstanceOf(item, Reactive) ? item.value : item, i)
154
+ fn.call(
155
+ proxy,
156
+ (isComputed(item) ? item.value : item) as Value<T>,
157
+ i
158
+ )
117
159
  );
118
160
  }
119
161
 
120
162
  return values;
121
163
  }
122
164
 
123
- on<E extends keyof Events<unknown>>(event: E, listener: Listener<Events<T>[E]>) {
124
- this.signal.on(event, listener);
165
+ on<T>(event: keyof Events<T>, listener: Listener<T>) {
166
+ if (this.listeners === null) {
167
+ this.listeners = { [event]: [listener] };
168
+ }
169
+ else {
170
+ let listeners = this.listeners[event];
171
+
172
+ if (listeners === undefined) {
173
+ this.listeners[event] = [listener];
174
+ }
175
+ else if (listeners.indexOf(listener) === -1) {
176
+ let i = listeners.indexOf(null);
177
+
178
+ if (i === -1) {
179
+ listeners.push(listener);
180
+ }
181
+ else {
182
+ listeners[i] = listener;
183
+ }
184
+ }
185
+ }
125
186
  }
126
187
 
127
- once<E extends keyof Events<unknown>>(event: E, listener: Listener<Events<T>[E]>) {
128
- this.signal.once(event, listener);
188
+ once<T>(event: keyof Events<T>, listener: Listener<T>) {
189
+ listener.once = true;
190
+ this.on(event, listener);
129
191
  }
130
192
 
131
193
  pop() {
132
194
  let item = this.data.pop();
133
195
 
134
196
  if (item !== undefined) {
135
- dispose(item);
136
- this.signal.dispatch('pop', { item });
197
+ if (isComputed(item)) {
198
+ dispose(item);
199
+ }
200
+
201
+ this.dispatch('pop', { item });
137
202
  }
138
203
 
139
204
  return item;
140
205
  }
141
206
 
142
207
  push(...input: T[]) {
143
- let items = factory(input, this.options),
208
+ let items = factory(input),
144
209
  n = this.data.push(...items);
145
210
 
146
- this.signal.dispatch('push', { items });
211
+ this.dispatch('push', { items });
147
212
 
148
213
  return n;
149
214
  }
150
215
 
151
216
  reverse() {
152
217
  this.data.reverse();
153
- this.signal.dispatch('reverse');
218
+ this.dispatch('reverse');
154
219
 
155
220
  return this;
156
221
  }
@@ -159,30 +224,40 @@ class ReactiveArray<T> {
159
224
  let item = this.data.shift();
160
225
 
161
226
  if (item !== undefined) {
162
- dispose(item);
163
- this.signal.dispatch('shift', { item });
227
+ if (isComputed(item)) {
228
+ dispose(item);
229
+ }
230
+
231
+ this.dispatch('shift', { item });
164
232
  }
165
233
 
166
234
  return item;
167
235
  }
168
236
 
169
- sort(fn: (a: T, b: T) => number) {
237
+ sort(fn: (a: Value<T>, b: Value<T>) => number) {
170
238
  this.data.sort((a, b) => fn(
171
- isInstanceOf(a, Reactive) ? a.value : a,
172
- isInstanceOf(b, Reactive) ? b.value : b
239
+ (isComputed(a) ? a.value : a) as Value<T>,
240
+ (isComputed(b) ? b.value : b) as Value<T>
173
241
  ));
174
- this.signal.dispatch('sort');
242
+ this.dispatch('sort');
175
243
 
176
244
  return this;
177
245
  }
178
246
 
179
247
  splice(start: number, deleteCount: number = this.data.length, ...input: T[]) {
180
- let items = factory(input, this.options),
248
+ let items = factory(input),
181
249
  removed = this.data.splice(start, deleteCount, ...items);
182
250
 
183
251
  if (items.length > 0 || removed.length > 0) {
184
- dispose(removed);
185
- this.signal.dispatch('splice', {
252
+ for (let i = 0, n = removed.length; i < n; i++) {
253
+ let item = removed[i];
254
+
255
+ if (isComputed(item)) {
256
+ dispose(item);
257
+ }
258
+ }
259
+
260
+ this.dispatch('splice', {
186
261
  deleteCount,
187
262
  items,
188
263
  start
@@ -193,29 +268,33 @@ class ReactiveArray<T> {
193
268
  }
194
269
 
195
270
  unshift(...input: T[]) {
196
- let items = factory(input, this.options),
271
+ let items = factory(input),
197
272
  length = this.data.unshift(...items);
198
273
 
199
- this.signal.dispatch('unshift', { items });
274
+ this.dispatch('unshift', { items });
200
275
 
201
276
  return length;
202
277
  }
203
278
  }
204
279
 
205
280
 
206
- function factory<T>(input: T[], options: Options = {}) {
281
+ function factory<T>(input: T[]) {
207
282
  let items: Item<T>[] = [];
208
283
 
209
284
  for (let i = 0, n = input.length; i < n; i++) {
210
285
  let value = input[i];
211
286
 
212
- if (isObject(value)) {
213
- // @ts-ignore
214
- items[i] = object(value, options);
287
+ if (isArray(value)) {
288
+ items[i] = array(value);
289
+ }
290
+ else if (isFunction(value)) {
291
+ items[i] = computed(value as Computed<T>['fn']);
292
+ }
293
+ else if (isObject(value)) {
294
+ items[i] = object(value);
215
295
  }
216
296
  else {
217
- // @ts-ignore
218
- items[i] = signal(value);
297
+ items[i] = value;
219
298
  }
220
299
  }
221
300
 
@@ -223,15 +302,14 @@ function factory<T>(input: T[], options: Options = {}) {
223
302
  }
224
303
 
225
304
 
226
- export default <T>(input: T[], options: Options = {}) => {
227
- let wrapped = factory(input, options),
228
- proxy = new Proxy({}, {
305
+ export default function array<T>(input: T[]) {
306
+ let proxy = new Proxy({}, {
229
307
  get(_: any, key: any) {
230
308
  if (isNumber(key)) {
231
309
  let value = wrapped[key];
232
310
 
233
- if (isInstanceOf(value, Reactive)) {
234
- return value.get();
311
+ if (isComputed(value)) {
312
+ return read(value);
235
313
  }
236
314
 
237
315
  return value;
@@ -246,11 +324,8 @@ export default <T>(input: T[], options: Options = {}) => {
246
324
  if (isNumber(key)) {
247
325
  let host = wrapped[key];
248
326
 
249
- if (host === undefined) {
250
- wrapped[key] = factory([value] as T[], options)[0];
251
- }
252
- else if (isInstanceOf(host, Reactive)) {
253
- host.set(value);
327
+ if (host === undefined || !isComputed(host)) {
328
+ wrapped[key] = factory([value] as T[])[0];
254
329
  }
255
330
  else {
256
331
  return false;
@@ -264,10 +339,11 @@ export default <T>(input: T[], options: Options = {}) => {
264
339
 
265
340
  return false;
266
341
  }
267
- }) as API<T>;
342
+ }) as API<T>,
343
+ wrapped = factory(input);
268
344
 
269
345
  let a = new ReactiveArray(wrapped, proxy);
270
346
 
271
347
  return proxy;
272
348
  };
273
- export type { API as ReactiveArray };
349
+ export { ReactiveArray };
@@ -0,0 +1,8 @@
1
+ class Disposable {
2
+ dispose(): void {
3
+ throw new Error('@esportsplus/reactivity: Disposable should not be instantiated directly.');
4
+ }
5
+ }
6
+
7
+
8
+ export { Disposable };
@@ -1,25 +1,31 @@
1
- import { isArray, isObject } from '@esportsplus/utilities';
2
- import { Options, Reactive } from '~/types';
3
- import { default as array } from './array';
4
- import { default as object } from './object';
1
+ import { isArray, isObject, isPromise } from '@esportsplus/utilities';
2
+ import { Reactive } from '~/types';
3
+ import array from './array';
4
+ import object from './object';
5
+ import promise from './promise';
5
6
 
6
7
 
7
8
  type Guard<T> =
8
- T extends { dispose: any } | { signals: any }
9
- ? { never: '[ dispose, signals ] are reserved keys' }
10
- : T extends Record<PropertyKey, unknown> | unknown[]
11
- ? T
12
- : never;
9
+ T extends (...args: unknown[]) => Promise<unknown>
10
+ ? T
11
+ : T extends { dispose: any } | { signals: any }
12
+ ? { never: '[ dispose, signals ] are reserved keys' }
13
+ : T extends Record<PropertyKey, unknown> | unknown[]
14
+ ? T
15
+ : never;
13
16
 
14
17
 
15
- export default <T>(data: Guard<T>, options: Options = {}) => {
18
+ export default <T>(data: Guard<T>) => {
16
19
  let value;
17
20
 
18
21
  if (isArray(data)) {
19
- value = array(data, options);
22
+ value = array(data);
20
23
  }
21
24
  else if (isObject(data)) {
22
- value = object(data as { [K in keyof T]: T[K] }, options);
25
+ value = object(data);
26
+ }
27
+ else if (isPromise(data)) {
28
+ value = promise(data);
23
29
  }
24
30
  else {
25
31
  throw new Error(`@esportsplus/reactivity: 'reactive' received invalid input - ${JSON.stringify(data)}`);
@@ -1,63 +1,99 @@
1
- import { defineProperty, isArray, isFunction, isObject } from '@esportsplus/utilities';
2
- import { computed, signal } from '~/signal';
3
- import { Computed, Infer, Options, Prettify, ReactiveArray, Signal } from '~/types';
4
- import { default as array } from './array';
1
+ import { defineProperty, isArray, isAsyncFunction, isFunction, isInstanceOf, isObject, Prettify } from '@esportsplus/utilities';
2
+ import array, { ReactiveArray } from './array';
3
+ import { computed, dispose, read, signal } from '~/signal';
4
+ import { Computed, Infer, Signal } from '~/types';
5
+ import { Disposable } from './disposable';
6
+ import promise from './promise';
5
7
 
6
8
 
7
- type API<T> = Prettify< { [K in keyof T]: Infer<T[K]> } & { dispose: VoidFunction } >;
9
+ type API<T extends Record<PropertyKey, unknown>> = Prettify<{ [K in keyof T]: Infer<T[K]> }> & ReactiveObject<T>;
8
10
 
9
11
 
10
- class ReactiveObject<T extends Record<PropertyKey, unknown>> {
11
- signals: Record<PropertyKey, Computed<any> | ReactiveArray<any> | ReactiveObject<any> | Signal<any>> = {};
12
+ let { set } = signal;
12
13
 
13
14
 
14
- constructor(data: T, options: Options = {}) {
15
- let signals = this.signals;
15
+ class ReactiveObject<T extends Record<PropertyKey, unknown>> extends Disposable {
16
+ private disposable: Record<
17
+ PropertyKey,
18
+ Computed<any> | ReactiveArray<any> | ReactiveObject<any>
19
+ > = {};
20
+
21
+
22
+ constructor(data: T) {
23
+ super();
24
+
25
+ let disposable = this.disposable,
26
+ triggers: Record<string, Signal<boolean>> = {};
16
27
 
17
28
  for (let key in data) {
18
29
  let value = data[key];
19
30
 
20
31
  if (isArray(value)) {
21
- let s = signals[key] = array(value, options);
32
+ let a = disposable[key] = array(value),
33
+ t = triggers[key] = signal(false);
22
34
 
23
35
  defineProperty(this, key, {
24
36
  enumerable: true,
25
37
  get() {
26
- return s;
38
+ read(t);
39
+ return a;
40
+ },
41
+ set(v: typeof value) {
42
+ set(t, !!t.value);
43
+ a = disposable[key] = array(v);
44
+ }
45
+ });
46
+ }
47
+ if (isAsyncFunction(value)) {
48
+ let p = promise(value);
49
+
50
+ defineProperty(this, key, {
51
+ enumerable: true,
52
+ get() {
53
+ return p;
27
54
  }
28
55
  });
29
56
  }
30
57
  else if (isFunction(value)) {
31
- let s = signals[key] = computed(value as Computed<T>['fn'], options);
58
+ let c = disposable[key] = computed(value as Computed<T>['fn']);
32
59
 
33
60
  defineProperty(this, key, {
34
61
  enumerable: true,
35
62
  get() {
36
- return s.get();
63
+ return read(c as Computed<T>);
37
64
  }
38
65
  });
39
66
  }
40
67
  else if (isObject(value)) {
41
- // Type issue with factory function below, fix after testing, if this is kept
42
- let s = signals[key] = new ReactiveObject(value, options);
68
+ let o = disposable[key] = new ReactiveObject(value),
69
+ t = triggers[key] = signal(false);
43
70
 
44
71
  defineProperty(this, key, {
45
72
  enumerable: true,
46
73
  get() {
47
- return s;
74
+ read(t);
75
+ return o;
76
+ },
77
+ set(v: typeof value) {
78
+ set(t, !!t.value);
79
+ o = disposable[key] = new ReactiveObject(v);
48
80
  }
49
81
  });
50
82
  }
51
83
  else {
52
- let s = signals[key] = signal(value, options);
84
+ let s = signal(value);
53
85
 
54
86
  defineProperty(this, key, {
55
87
  enumerable: true,
56
88
  get() {
57
- return s.get();
89
+ if (s === undefined) {
90
+ s = signal(value);
91
+ }
92
+
93
+ return read(s as Signal<typeof value>);
58
94
  },
59
- set(value) {
60
- s.set(value);
95
+ set(v: typeof value) {
96
+ set(s, v);
61
97
  }
62
98
  });
63
99
  }
@@ -66,16 +102,23 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> {
66
102
 
67
103
 
68
104
  dispose() {
69
- let signals = this.signals;
105
+ for (let key in this.disposable) {
106
+ let value = this.disposable[key];
70
107
 
71
- for (let key in signals) {
72
- signals[key].dispose();
108
+ if (isInstanceOf(value, Disposable)) {
109
+ value.dispose();
110
+ }
111
+ else {
112
+ dispose(value);
113
+ }
73
114
  }
115
+
116
+ this.disposable = {};
74
117
  }
75
118
  }
76
119
 
77
120
 
78
- export default function object<T extends Record<PropertyKey, unknown>>(input: T, options: Options = {}) {
79
- return new ReactiveObject(input, options) as API<T>;
121
+ export default function object<T extends Record<PropertyKey, unknown>>(input: T) {
122
+ return new ReactiveObject(input) as API<T>;
80
123
  };
81
- export type { API as ReactiveObject };
124
+ export { ReactiveObject };