@esportsplus/reactivity 0.8.1 → 0.9.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/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { default as reactive } from './reactive/index.js';
2
- export * from './signal.js';
2
+ export * from './system.js';
3
3
  export * from './types.js';
package/build/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { default as reactive } from './reactive/index.js';
2
- export * from './signal.js';
2
+ export * from './system.js';
3
3
  export * from './types.js';
@@ -1,5 +1,5 @@
1
1
  import { isFunction, isInstanceOf, isNumber, isObject } from '@esportsplus/utilities';
2
- import { computed, dispose, isComputed, read } from '../signal.js';
2
+ import { computed, dispose, isComputed, read } from '../system.js';
3
3
  import object from './object.js';
4
4
  import { Disposable } from './disposable.js';
5
5
  class ReactiveArray extends Disposable {
@@ -1,5 +1,5 @@
1
1
  import CustomFunction from '@esportsplus/custom-function';
2
- import { read, root, signal } from '../signal.js';
2
+ import { read, root, signal } from '../system.js';
3
3
  let { set } = signal;
4
4
  class ReactiveAsyncFunction extends CustomFunction {
5
5
  arguments;
@@ -1,9 +1,8 @@
1
- import { Computed } from '../types.js';
2
1
  import array from './array.js';
3
2
  import async from './async.js';
4
3
  import object from './object.js';
5
- type API<T> = T extends (...args: infer A) => Promise<infer R> ? ReturnType<typeof async<A, Promise<R>>> : T extends (...args: unknown[]) => unknown ? void : T extends Record<PropertyKey, unknown> ? ReturnType<typeof object<T>> : T extends unknown[] ? ReturnType<typeof array<T>> : never;
6
- type Input<T> = T extends (...args: unknown[]) => Promise<unknown> ? T : T extends (...args: unknown[]) => unknown ? Computed<T>['fn'] : T extends {
4
+ type API<T> = T extends (...args: infer A) => Promise<infer R> ? ReturnType<typeof async<A, Promise<R>>> : T extends Record<PropertyKey, unknown> ? ReturnType<typeof object<T>> : T extends unknown[] ? ReturnType<typeof array<T>> : never;
5
+ type Input<T> = T extends (...args: unknown[]) => Promise<unknown> ? T : T extends {
7
6
  dispose: any;
8
7
  } | {
9
8
  signals: any;
@@ -1,5 +1,4 @@
1
- import { isArray, isAsyncFunction, isFunction, isObject } from '@esportsplus/utilities';
2
- import { computed } from '../signal.js';
1
+ import { isArray, isAsyncFunction, isObject } from '@esportsplus/utilities';
3
2
  import array from './array.js';
4
3
  import async from './async.js';
5
4
  import object from './object.js';
@@ -10,10 +9,6 @@ export default (input) => {
10
9
  else if (isAsyncFunction(input)) {
11
10
  return async(input);
12
11
  }
13
- else if (isFunction(input)) {
14
- computed(input);
15
- return undefined;
16
- }
17
12
  else if (isObject(input)) {
18
13
  return object(input);
19
14
  }
@@ -1,6 +1,6 @@
1
1
  import { defineProperty, isArray, isAsyncFunction, isFunction, isInstanceOf } from '@esportsplus/utilities';
2
2
  import array from './array.js';
3
- import { computed, dispose, read, signal } from '../signal.js';
3
+ import { computed, dispose, read, signal } from '../system.js';
4
4
  import { Disposable } from './disposable.js';
5
5
  import async from './async.js';
6
6
  let { set } = signal;
@@ -1,6 +1,7 @@
1
1
  import { Computed, Signal } from './types.js';
2
2
  declare const computed: <T>(fn: Computed<T>["fn"]) => Computed<T>;
3
3
  declare const dispose: <T>(computed: Computed<T>) => void;
4
+ declare const effect: <T>(fn: Computed<T>["fn"]) => void;
4
5
  declare const isComputed: (value: unknown) => value is Computed<unknown>;
5
6
  declare const isSignal: (value: unknown) => value is Signal<unknown>;
6
7
  declare const onCleanup: (fn: VoidFunction) => typeof fn;
@@ -11,4 +12,4 @@ declare const signal: {
11
12
  set<T>(signal: Signal<T>, value: T): void;
12
13
  };
13
14
  declare const stabilize: () => void;
14
- export { computed, dispose, isComputed, isSignal, onCleanup, read, root, signal, stabilize };
15
+ export { computed, dispose, effect, isComputed, isSignal, onCleanup, read, root, signal, stabilize };
@@ -1,6 +1,6 @@
1
1
  import { defineProperty, isArray, isObject } from '@esportsplus/utilities';
2
2
  import { REACTIVE, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants.js';
3
- let heap = new Array(2000), iHeap = 0, nHeap = 0, notifiedHeap = false, observer = null, scheduled = false, scheduler = null;
3
+ let depth = 0, heap = new Array(2000), index = 0, length = 0, notified = false, observer = null, scheduled = false, scheduler = null, version = 0;
4
4
  function cleanup(node) {
5
5
  if (!node.cleanup) {
6
6
  return;
@@ -55,8 +55,8 @@ function insertIntoHeap(computed) {
55
55
  computed.prevHeap = tail;
56
56
  heapAtHeight.prevHeap = computed;
57
57
  }
58
- if (height > nHeap) {
59
- nHeap = height;
58
+ if (height > length) {
59
+ length = height;
60
60
  if (height >= heap.length) {
61
61
  heap.length += 250;
62
62
  }
@@ -71,17 +71,25 @@ function link(dep, sub) {
71
71
  if (sub.state & STATE_RECOMPUTING) {
72
72
  nextDep = prevDep !== null ? prevDep.nextDep : sub.deps;
73
73
  if (nextDep !== null && nextDep.dep === dep) {
74
+ nextDep.version = version;
74
75
  sub.depsTail = nextDep;
75
76
  return;
76
77
  }
77
78
  }
78
- let prevSub = dep.subsTail, newLink = sub.depsTail =
79
+ let prevSub = dep.subsTail;
80
+ if (prevSub !== null &&
81
+ prevSub.version === version &&
82
+ prevSub.sub === sub) {
83
+ return;
84
+ }
85
+ let newLink = sub.depsTail =
79
86
  dep.subsTail = {
80
87
  dep,
81
88
  sub,
82
89
  nextDep,
83
90
  prevSub,
84
91
  nextSub: null,
92
+ version
85
93
  };
86
94
  if (prevDep !== null) {
87
95
  prevDep.nextDep = newLink;
@@ -119,6 +127,8 @@ function recompute(computed, del) {
119
127
  observer = computed;
120
128
  computed.depsTail = null;
121
129
  computed.state = STATE_RECOMPUTING;
130
+ depth++;
131
+ version++;
122
132
  try {
123
133
  value = computed.fn(onCleanup);
124
134
  }
@@ -149,16 +159,18 @@ function recompute(computed, del) {
149
159
  insertIntoHeap(o);
150
160
  }
151
161
  }
152
- if (!scheduled && scheduler) {
153
- scheduled = true;
154
- scheduler(stabilize);
155
- }
156
- else {
157
- throw new Error('@esportsplus/reactivity: stabilize.scheduler has not been set to process updates.');
162
+ if (!--depth) {
163
+ if (!scheduled && scheduler) {
164
+ scheduled = true;
165
+ scheduler(stabilize);
166
+ }
167
+ else {
168
+ throw new Error('@esportsplus/reactivity: stabilize.scheduler has not been set to process updates.');
169
+ }
158
170
  }
159
171
  }
160
172
  function unlink(link) {
161
- let dep = link.dep, nextDep = link.nextDep, nextSub = link.nextSub, prevSub = link.prevSub;
173
+ let { dep, nextDep, nextSub, prevSub } = link;
162
174
  if (nextSub !== null) {
163
175
  nextSub.prevSub = prevSub;
164
176
  }
@@ -234,6 +246,9 @@ const dispose = (computed) => {
234
246
  computed.deps = null;
235
247
  cleanup(computed);
236
248
  };
249
+ const effect = (fn) => {
250
+ computed(fn);
251
+ };
237
252
  const isComputed = (value) => {
238
253
  return isObject(value) && REACTIVE in value && 'fn' in value;
239
254
  };
@@ -264,11 +279,11 @@ const read = (node) => {
264
279
  if (height >= observer.height) {
265
280
  observer.height = height + 1;
266
281
  }
267
- if (height >= iHeap ||
282
+ if (height >= index ||
268
283
  node.state & (STATE_DIRTY | STATE_CHECK)) {
269
- if (!notifiedHeap) {
270
- notifiedHeap = true;
271
- for (let i = 0; i <= nHeap; i++) {
284
+ if (!notified) {
285
+ notified = true;
286
+ for (let i = 0; i <= length; i++) {
272
287
  for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
273
288
  notify(computed);
274
289
  }
@@ -299,7 +314,7 @@ signal.set = (signal, value) => {
299
314
  if (signal.value === value) {
300
315
  return;
301
316
  }
302
- notifiedHeap = false;
317
+ notified = false;
303
318
  signal.value = value;
304
319
  for (let link = signal.subs; link !== null; link = link.nextSub) {
305
320
  insertIntoHeap(link.sub);
@@ -307,9 +322,9 @@ signal.set = (signal, value) => {
307
322
  };
308
323
  const stabilize = () => {
309
324
  root(() => {
310
- for (iHeap = 0; iHeap <= nHeap; iHeap++) {
311
- let computed = heap[iHeap];
312
- heap[iHeap] = undefined;
325
+ for (index = 0; index <= length; index++) {
326
+ let computed = heap[index];
327
+ heap[index] = undefined;
313
328
  while (computed !== undefined) {
314
329
  let next = computed.nextHeap;
315
330
  recompute(computed, false);
@@ -327,4 +342,4 @@ defineProperty(stabilize, 'scheduler', {
327
342
  scheduler = s;
328
343
  },
329
344
  });
330
- export { computed, dispose, isComputed, isSignal, onCleanup, read, root, signal, stabilize };
345
+ export { computed, dispose, effect, isComputed, isSignal, onCleanup, read, root, signal, stabilize };
package/build/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { REACTIVE, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants.js';
2
- import { onCleanup } from './signal.js';
2
+ import { onCleanup } from './system.js';
3
3
  import { ReactiveArray } from './reactive/array.js';
4
4
  import { ReactiveObject } from './reactive/object.js';
5
5
  interface Computed<T> extends Signal<T> {
@@ -22,6 +22,7 @@ interface Link {
22
22
  nextDep: Link | null;
23
23
  nextSub: Link | null;
24
24
  prevSub: Link | null;
25
+ version: number;
25
26
  }
26
27
  type Reactive<T> = T extends Record<PropertyKey, unknown> ? ReactiveObject<T> : ReactiveArray<T>;
27
28
  type Signal<T> = {
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.8.1",
15
+ "version": "0.9.0",
16
16
  "scripts": {
17
17
  "build": "tsc && tsc-alias",
18
18
  "-": "-"
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { default as reactive } from './reactive';
2
- export * from './signal';
2
+ export * from './system';
3
3
  export * from './types';
@@ -1,5 +1,5 @@
1
1
  import { isFunction, isInstanceOf, isNumber, isObject } from '@esportsplus/utilities';
2
- import { computed, dispose, isComputed, read } from '~/signal';
2
+ import { computed, dispose, isComputed, read } from '~/system';
3
3
  import { Computed, Infer } from '~/types';
4
4
  import object, { ReactiveObject } from './object';
5
5
  import { Disposable } from './disposable';
@@ -1,5 +1,5 @@
1
1
  import CustomFunction from '@esportsplus/custom-function';
2
- import { read, root, signal } from '~/signal';
2
+ import { read, root, signal } from '~/system';
3
3
  import { Signal } from '~/types';
4
4
 
5
5
 
@@ -1,6 +1,4 @@
1
- import { isArray, isAsyncFunction, isFunction, isObject } from '@esportsplus/utilities';
2
- import { computed } from '~/signal';
3
- import { Computed } from '~/types';
1
+ import { isArray, isAsyncFunction, isObject } from '@esportsplus/utilities';
4
2
  import array from './array';
5
3
  import async from './async';
6
4
  import object from './object';
@@ -9,24 +7,20 @@ import object from './object';
9
7
  type API<T> =
10
8
  T extends (...args: infer A) => Promise<infer R>
11
9
  ? ReturnType<typeof async<A, Promise<R>>>
12
- : T extends (...args: unknown[]) => unknown
13
- ? void
14
- : T extends Record<PropertyKey, unknown>
15
- ? ReturnType<typeof object<T>>
16
- : T extends unknown[]
17
- ? ReturnType<typeof array<T>>
18
- : never;
10
+ : T extends Record<PropertyKey, unknown>
11
+ ? ReturnType<typeof object<T>>
12
+ : T extends unknown[]
13
+ ? ReturnType<typeof array<T>>
14
+ : never;
19
15
 
20
16
  type Input<T> =
21
17
  T extends (...args: unknown[]) => Promise<unknown>
22
18
  ? T
23
- : T extends (...args: unknown[]) => unknown
24
- ? Computed<T>['fn']
25
- : T extends { dispose: any } | { signals: any }
26
- ? { never: '[ dispose, signals ] are reserved keys' }
27
- : T extends Record<PropertyKey, unknown> | unknown[]
28
- ? T
29
- : never;
19
+ : T extends { dispose: any } | { signals: any }
20
+ ? { never: '[ dispose, signals ] are reserved keys' }
21
+ : T extends Record<PropertyKey, unknown> | unknown[]
22
+ ? T
23
+ : never;
30
24
 
31
25
 
32
26
  export default <T>(input: Input<T>): API<T> => {
@@ -36,10 +30,6 @@ export default <T>(input: Input<T>): API<T> => {
36
30
  else if (isAsyncFunction(input)) {
37
31
  return async(input) as API<T>;
38
32
  }
39
- else if (isFunction(input)) {
40
- computed(input as Computed<T>['fn']);
41
- return undefined as API<T>;
42
- }
43
33
  else if (isObject(input)) {
44
34
  return object(input) as API<T>;
45
35
  }
@@ -1,6 +1,6 @@
1
1
  import { defineProperty, isArray, isAsyncFunction, isFunction, isInstanceOf, Prettify } from '@esportsplus/utilities';
2
2
  import array, { ReactiveArray } from './array';
3
- import { computed, dispose, read, signal } from '~/signal';
3
+ import { computed, dispose, read, signal } from '~/system';
4
4
  import { Computed, Infer, Signal } from '~/types';
5
5
  import { Disposable } from './disposable';
6
6
  import async from './async';
@@ -3,13 +3,15 @@ import { REACTIVE, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RE
3
3
  import { Computed, Link, Signal, } from './types';
4
4
 
5
5
 
6
- let heap: (Computed<unknown> | undefined)[] = new Array(2000),
7
- iHeap = 0,
8
- nHeap = 0,
9
- notifiedHeap = false,
6
+ let depth = 0,
7
+ heap: (Computed<unknown> | undefined)[] = new Array(2000),
8
+ index = 0,
9
+ length = 0,
10
+ notified = false,
10
11
  observer: Computed<unknown> | null = null,
11
12
  scheduled = false,
12
- scheduler: ((task: VoidFunction) => void) | null = null;
13
+ scheduler: ((task: VoidFunction) => void) | null = null,
14
+ version = 0;
13
15
 
14
16
 
15
17
  function cleanup<T>(node: Computed<T>): void {
@@ -87,8 +89,8 @@ function insertIntoHeap<T>(computed: Computed<T>) {
87
89
  heapAtHeight.prevHeap = computed;
88
90
  }
89
91
 
90
- if (height > nHeap) {
91
- nHeap = height;
92
+ if (height > length) {
93
+ length = height;
92
94
 
93
95
  // Simple auto adjust to avoid manual management within apps.
94
96
  if (height >= heap.length) {
@@ -111,13 +113,24 @@ function link<T>(dep: Signal<T> | Computed<T>, sub: Computed<T>) {
111
113
  nextDep = prevDep !== null ? prevDep.nextDep : sub.deps;
112
114
 
113
115
  if (nextDep !== null && nextDep.dep === dep) {
116
+ nextDep.version = version;
114
117
  sub.depsTail = nextDep;
115
118
  return;
116
119
  }
117
120
  }
118
121
 
119
- let prevSub = dep.subsTail,
120
- newLink =
122
+ let prevSub = dep.subsTail;
123
+
124
+ // https://github.com/stackblitz/alien-signals/commit/54fe1b3947fac5c0aecb73b0b0eaff000806c454
125
+ if (
126
+ prevSub !== null &&
127
+ prevSub.version === version &&
128
+ prevSub.sub === sub
129
+ ) {
130
+ return;
131
+ }
132
+
133
+ let newLink =
121
134
  sub.depsTail =
122
135
  dep.subsTail = {
123
136
  dep,
@@ -125,6 +138,7 @@ function link<T>(dep: Signal<T> | Computed<T>, sub: Computed<T>) {
125
138
  nextDep,
126
139
  prevSub,
127
140
  nextSub: null,
141
+ version
128
142
  };
129
143
 
130
144
  if (prevDep !== null) {
@@ -175,6 +189,9 @@ function recompute<T>(computed: Computed<T>, del: boolean) {
175
189
  computed.depsTail = null;
176
190
  computed.state = STATE_RECOMPUTING;
177
191
 
192
+ depth++;
193
+ version++;
194
+
178
195
  try {
179
196
  value = computed.fn(onCleanup);
180
197
  }
@@ -217,21 +234,20 @@ function recompute<T>(computed: Computed<T>, del: boolean) {
217
234
  }
218
235
  }
219
236
 
220
- if (!scheduled && scheduler) {
221
- scheduled = true;
222
- scheduler(stabilize);
223
- }
224
- else {
225
- throw new Error('@esportsplus/reactivity: stabilize.scheduler has not been set to process updates.');
237
+ if (!--depth) {
238
+ if (!scheduled && scheduler) {
239
+ scheduled = true;
240
+ scheduler(stabilize);
241
+ }
242
+ else {
243
+ throw new Error('@esportsplus/reactivity: stabilize.scheduler has not been set to process updates.');
244
+ }
226
245
  }
227
246
  }
228
247
 
229
248
  // https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L100
230
249
  function unlink(link: Link): Link | null {
231
- let dep = link.dep,
232
- nextDep = link.nextDep,
233
- nextSub = link.nextSub,
234
- prevSub = link.prevSub;
250
+ let { dep, nextDep, nextSub, prevSub } = link;
235
251
 
236
252
  if (nextSub !== null) {
237
253
  nextSub.prevSub = prevSub;
@@ -328,6 +344,10 @@ const dispose = <T>(computed: Computed<T>) => {
328
344
  cleanup(computed);
329
345
  };
330
346
 
347
+ const effect = <T>(fn: Computed<T>['fn']) => {
348
+ computed(fn);
349
+ };
350
+
331
351
  const isComputed = (value: unknown): value is Computed<unknown> => {
332
352
  return isObject(value) && REACTIVE in value && 'fn' in value;
333
353
  };
@@ -368,13 +388,13 @@ const read = <T>(node: Signal<T> | Computed<T>): T => {
368
388
  }
369
389
 
370
390
  if (
371
- height >= iHeap ||
391
+ height >= index ||
372
392
  node.state & (STATE_DIRTY | STATE_CHECK)
373
393
  ) {
374
- if (!notifiedHeap) {
375
- notifiedHeap = true;
394
+ if (!notified) {
395
+ notified = true;
376
396
 
377
- for (let i = 0; i <= nHeap; i++) {
397
+ for (let i = 0; i <= length; i++) {
378
398
  for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
379
399
  notify(computed);
380
400
  }
@@ -415,7 +435,7 @@ signal.set = <T>(signal: Signal<T>, value: T) => {
415
435
  return;
416
436
  }
417
437
 
418
- notifiedHeap = false;
438
+ notified = false;
419
439
  signal.value = value;
420
440
 
421
441
  for (let link = signal.subs; link !== null; link = link.nextSub) {
@@ -425,10 +445,10 @@ signal.set = <T>(signal: Signal<T>, value: T) => {
425
445
 
426
446
  const stabilize = () => {
427
447
  root(() => {
428
- for (iHeap = 0; iHeap <= nHeap; iHeap++) {
429
- let computed = heap[iHeap];
448
+ for (index = 0; index <= length; index++) {
449
+ let computed = heap[index];
430
450
 
431
- heap[iHeap] = undefined;
451
+ heap[index] = undefined;
432
452
 
433
453
  while (computed !== undefined) {
434
454
  let next = computed.nextHeap;
@@ -456,6 +476,7 @@ defineProperty(stabilize, 'scheduler', {
456
476
  export {
457
477
  computed,
458
478
  dispose,
479
+ effect,
459
480
  isComputed, isSignal,
460
481
  onCleanup,
461
482
  read, root,
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { REACTIVE, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants';
2
- import { onCleanup } from './signal';
2
+ import { onCleanup } from './system';
3
3
  import { ReactiveArray } from './reactive/array';
4
4
  import { ReactiveObject } from './reactive/object';
5
5
 
@@ -38,6 +38,7 @@ interface Link {
38
38
  nextDep: Link | null;
39
39
  nextSub: Link | null;
40
40
  prevSub: Link | null;
41
+ version: number;
41
42
  }
42
43
 
43
44
  type Reactive<T> = T extends Record<PropertyKey, unknown>