@esportsplus/reactivity 0.8.0 → 0.8.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/build/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export { default as reactive } from './reactive/index.js';
2
- export { computed, dispose, oncleanup, root, signal, stabilize } from './signal.js';
3
- export { default as scheduler } from './scheduler.js';
2
+ export * from './signal.js';
4
3
  export * from './types.js';
package/build/index.js CHANGED
@@ -1,4 +1,3 @@
1
1
  export { default as reactive } from './reactive/index.js';
2
- export { computed, dispose, oncleanup, root, signal, stabilize } from './signal.js';
3
- export { default as scheduler } from './scheduler.js';
2
+ export * from './signal.js';
4
3
  export * from './types.js';
@@ -1,5 +1,5 @@
1
1
  import CustomFunction from '@esportsplus/custom-function';
2
- declare class ReactivePromise<A extends unknown[], R extends Promise<unknown>> extends CustomFunction {
2
+ declare class ReactiveAsyncFunction<A extends unknown[], R extends Promise<unknown>> extends CustomFunction {
3
3
  private arguments;
4
4
  private okay;
5
5
  private response;
@@ -9,5 +9,5 @@ declare class ReactivePromise<A extends unknown[], R extends Promise<unknown>> e
9
9
  get input(): A | null;
10
10
  get ok(): boolean | null;
11
11
  }
12
- declare const _default: <A extends unknown[], R extends Promise<unknown>>(fn: (...args: A) => R) => ReactivePromise<A, R>;
12
+ declare const _default: <A extends unknown[], R extends Promise<unknown>>(fn: (...args: A) => R) => ReactiveAsyncFunction<A, R>;
13
13
  export default _default;
@@ -1,7 +1,7 @@
1
1
  import CustomFunction from '@esportsplus/custom-function';
2
2
  import { read, root, signal } from '../signal.js';
3
3
  let { set } = signal;
4
- class ReactivePromise extends CustomFunction {
4
+ class ReactiveAsyncFunction extends CustomFunction {
5
5
  arguments;
6
6
  okay;
7
7
  response;
@@ -44,5 +44,5 @@ class ReactivePromise extends CustomFunction {
44
44
  }
45
45
  }
46
46
  export default (fn) => {
47
- return new ReactivePromise(fn);
47
+ return new ReactiveAsyncFunction(fn);
48
48
  };
@@ -1,13 +1,14 @@
1
+ import { Computed } from '../types.js';
1
2
  import array from './array.js';
3
+ import async from './async.js';
2
4
  import object from './object.js';
3
- import promise from './promise.js';
4
- type API<T> = T extends (...args: infer A) => Promise<infer R> ? ReturnType<typeof promise<A, Promise<R>>> : T extends Record<PropertyKey, unknown> ? ReturnType<typeof object<T>> : T extends unknown[] ? ReturnType<typeof array<T>> : never;
5
- type Guard<T> = T extends (...args: unknown[]) => Promise<unknown> ? T : T extends {
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 {
6
7
  dispose: any;
7
8
  } | {
8
9
  signals: any;
9
10
  } ? {
10
11
  never: '[ dispose, signals ] are reserved keys';
11
12
  } : T extends Record<PropertyKey, unknown> | unknown[] ? T : never;
12
- declare const _default: <T>(data: Guard<T>) => API<T>;
13
+ declare const _default: <T>(input: Input<T>) => API<T>;
13
14
  export default _default;
@@ -1,16 +1,21 @@
1
- import { isArray, isObject, isPromise } from '@esportsplus/utilities';
1
+ import { isArray, isAsyncFunction, isFunction, isObject } from '@esportsplus/utilities';
2
+ import { computed } from '../signal.js';
2
3
  import array from './array.js';
4
+ import async from './async.js';
3
5
  import object from './object.js';
4
- import promise from './promise.js';
5
- export default (data) => {
6
- if (isArray(data)) {
7
- return array(data);
6
+ export default (input) => {
7
+ if (isArray(input)) {
8
+ return array(input);
8
9
  }
9
- else if (isObject(data)) {
10
- return object(data);
10
+ else if (isAsyncFunction(input)) {
11
+ return async(input);
11
12
  }
12
- else if (isPromise(data)) {
13
- return promise(data);
13
+ else if (isFunction(input)) {
14
+ computed(input);
15
+ return undefined;
14
16
  }
15
- throw new Error(`@esportsplus/reactivity: 'reactive' received invalid input - ${JSON.stringify(data)}`);
17
+ else if (isObject(input)) {
18
+ return object(input);
19
+ }
20
+ throw new Error(`@esportsplus/reactivity: 'reactive' received invalid input - ${JSON.stringify(input)}`);
16
21
  };
@@ -2,7 +2,7 @@ import { defineProperty, isArray, isAsyncFunction, isFunction, isInstanceOf } fr
2
2
  import array from './array.js';
3
3
  import { computed, dispose, read, signal } from '../signal.js';
4
4
  import { Disposable } from './disposable.js';
5
- import promise from './promise.js';
5
+ import async from './async.js';
6
6
  let { set } = signal;
7
7
  class ReactiveObject extends Disposable {
8
8
  disposable = {};
@@ -26,7 +26,7 @@ class ReactiveObject extends Disposable {
26
26
  });
27
27
  }
28
28
  else if (isAsyncFunction(value)) {
29
- let p = promise(value);
29
+ let p = async(value);
30
30
  defineProperty(this, key, {
31
31
  enumerable: true,
32
32
  get() {
package/build/signal.d.ts CHANGED
@@ -1,18 +1,14 @@
1
1
  import { Computed, Signal } from './types.js';
2
2
  declare const computed: <T>(fn: Computed<T>["fn"]) => Computed<T>;
3
- declare const dispose: <T>(el: Computed<T>) => void;
3
+ declare const dispose: <T>(computed: Computed<T>) => void;
4
4
  declare const isComputed: (value: unknown) => value is Computed<unknown>;
5
- declare const isReactive: (value: unknown) => value is Computed<unknown> | Signal<unknown>;
6
5
  declare const isSignal: (value: unknown) => value is Signal<unknown>;
7
- declare const oncleanup: (fn: VoidFunction) => typeof fn;
8
- declare const read: <T>(el: Signal<T> | Computed<T>) => T;
6
+ declare const onCleanup: (fn: VoidFunction) => typeof fn;
7
+ declare const read: <T>(node: Signal<T> | Computed<T>) => T;
9
8
  declare const root: <T>(fn: () => T) => T;
10
9
  declare const signal: {
11
10
  <T>(value: T): Signal<T>;
12
- set<T>(el: Signal<T>, v: T): void;
11
+ set<T>(signal: Signal<T>, value: T): void;
13
12
  };
14
- declare const stabilize: {
15
- (): void;
16
- state: Signal<number>;
17
- };
18
- export { computed, dispose, isComputed, isReactive, isSignal, oncleanup, read, root, signal, stabilize };
13
+ declare const stabilize: () => void;
14
+ export { computed, dispose, isComputed, isSignal, onCleanup, read, root, signal, stabilize };
package/build/signal.js CHANGED
@@ -1,63 +1,64 @@
1
- import { isArray, isObject } from '@esportsplus/utilities';
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 dirtyHeap = new Array(2000), maxDirty = 0, markedHeap = false, minDirty = 0, observer = null;
3
+ let heap = new Array(2000), iHeap = 0, nHeap = 0, notifiedHeap = false, observer = null, scheduled = false, scheduler = null;
4
4
  function cleanup(node) {
5
5
  if (!node.cleanup) {
6
6
  return;
7
7
  }
8
- if (isArray(node.cleanup)) {
9
- for (let i = 0; i < node.cleanup.length; i++) {
10
- node.cleanup[i]();
8
+ let cleanup = node.cleanup;
9
+ if (isArray(cleanup)) {
10
+ for (let i = 0; i < cleanup.length; i++) {
11
+ cleanup[i]();
11
12
  }
12
13
  }
13
14
  else {
14
- node.cleanup();
15
+ cleanup();
15
16
  }
16
17
  node.cleanup = null;
17
18
  }
18
- function deleteFromHeap(n) {
19
- let state = n.state;
19
+ function deleteFromHeap(computed) {
20
+ let state = computed.state;
20
21
  if (!(state & STATE_IN_HEAP)) {
21
22
  return;
22
23
  }
23
- n.state = state & ~STATE_IN_HEAP;
24
- let height = n.height;
25
- if (n.prevHeap === n) {
26
- dirtyHeap[height] = undefined;
24
+ computed.state = state & ~STATE_IN_HEAP;
25
+ let height = computed.height;
26
+ if (computed.prevHeap === computed) {
27
+ heap[height] = undefined;
27
28
  }
28
29
  else {
29
- let next = n.nextHeap, dhh = dirtyHeap[height], end = next ?? dhh;
30
- if (n === dhh) {
31
- dirtyHeap[height] = next;
30
+ let next = computed.nextHeap, dhh = heap[height], end = next ?? dhh;
31
+ if (computed === dhh) {
32
+ heap[height] = next;
32
33
  }
33
34
  else {
34
- n.prevHeap.nextHeap = next;
35
+ computed.prevHeap.nextHeap = next;
35
36
  }
36
- end.prevHeap = n.prevHeap;
37
+ end.prevHeap = computed.prevHeap;
37
38
  }
38
- n.nextHeap = undefined;
39
- n.prevHeap = n;
39
+ computed.nextHeap = undefined;
40
+ computed.prevHeap = computed;
40
41
  }
41
- function insertIntoHeap(n) {
42
- let state = n.state;
42
+ function insertIntoHeap(computed) {
43
+ let state = computed.state;
43
44
  if (state & STATE_IN_HEAP) {
44
45
  return;
45
46
  }
46
- n.state = state | STATE_IN_HEAP;
47
- let height = n.height, heapAtHeight = dirtyHeap[height];
47
+ computed.state = state | STATE_IN_HEAP;
48
+ let height = computed.height, heapAtHeight = heap[height];
48
49
  if (heapAtHeight === undefined) {
49
- dirtyHeap[height] = n;
50
+ heap[height] = computed;
50
51
  }
51
52
  else {
52
53
  let tail = heapAtHeight.prevHeap;
53
- tail.nextHeap = n;
54
- n.prevHeap = tail;
55
- heapAtHeight.prevHeap = n;
56
- }
57
- if (height > maxDirty) {
58
- maxDirty = height;
59
- if (height >= dirtyHeap.length) {
60
- dirtyHeap.length += 250;
54
+ tail.nextHeap = computed;
55
+ computed.prevHeap = tail;
56
+ heapAtHeight.prevHeap = computed;
57
+ }
58
+ if (height > nHeap) {
59
+ nHeap = height;
60
+ if (height >= heap.length) {
61
+ heap.length += 250;
61
62
  }
62
63
  }
63
64
  }
@@ -95,49 +96,38 @@ function link(dep, sub) {
95
96
  dep.subs = newLink;
96
97
  }
97
98
  }
98
- function markHeap() {
99
- if (markedHeap) {
100
- return;
101
- }
102
- markedHeap = true;
103
- for (let i = 0; i <= maxDirty; i++) {
104
- for (let el = dirtyHeap[i]; el !== undefined; el = el.nextHeap) {
105
- markNode(el);
106
- }
107
- }
108
- }
109
- function markNode(el, newState = STATE_DIRTY) {
110
- let state = el.state;
99
+ function notify(computed, newState = STATE_DIRTY) {
100
+ let state = computed.state;
111
101
  if ((state & (STATE_CHECK | STATE_DIRTY)) >= newState) {
112
102
  return;
113
103
  }
114
- el.state = state | newState;
115
- for (let link = el.subs; link !== null; link = link.nextSub) {
116
- markNode(link.sub, STATE_CHECK);
104
+ computed.state = state | newState;
105
+ for (let link = computed.subs; link !== null; link = link.nextSub) {
106
+ notify(link.sub, STATE_CHECK);
117
107
  }
118
108
  }
119
- function recompute(el, del) {
109
+ function recompute(computed, del) {
120
110
  if (del) {
121
- deleteFromHeap(el);
111
+ deleteFromHeap(computed);
122
112
  }
123
113
  else {
124
- el.nextHeap = undefined;
125
- el.prevHeap = el;
114
+ computed.nextHeap = undefined;
115
+ computed.prevHeap = computed;
126
116
  }
127
- cleanup(el);
117
+ cleanup(computed);
128
118
  let o = observer, ok = true, value;
129
- observer = el;
130
- el.depsTail = null;
131
- el.state = STATE_RECOMPUTING;
119
+ observer = computed;
120
+ computed.depsTail = null;
121
+ computed.state = STATE_RECOMPUTING;
132
122
  try {
133
- value = el.fn(oncleanup);
123
+ value = computed.fn(onCleanup);
134
124
  }
135
125
  catch (e) {
136
126
  ok = false;
137
127
  }
138
128
  observer = o;
139
- el.state = STATE_NONE;
140
- let depsTail = el.depsTail, toRemove = depsTail !== null ? depsTail.nextDep : el.deps;
129
+ computed.state = STATE_NONE;
130
+ let depsTail = computed.depsTail, toRemove = depsTail !== null ? depsTail.nextDep : computed.deps;
141
131
  if (toRemove !== null) {
142
132
  do {
143
133
  toRemove = unlink(toRemove);
@@ -146,21 +136,25 @@ function recompute(el, del) {
146
136
  depsTail.nextDep = null;
147
137
  }
148
138
  else {
149
- el.deps = null;
139
+ computed.deps = null;
150
140
  }
151
141
  }
152
- if (ok && value !== el.value) {
153
- el.value = value;
154
- for (let s = el.subs; s !== null; s = s.nextSub) {
155
- let o = s.sub, state = o.state;
142
+ if (ok && value !== computed.value) {
143
+ computed.value = value;
144
+ for (let c = computed.subs; c !== null; c = c.nextSub) {
145
+ let o = c.sub, state = o.state;
156
146
  if (state & STATE_CHECK) {
157
147
  o.state = state | STATE_DIRTY;
158
148
  }
159
149
  insertIntoHeap(o);
160
150
  }
161
151
  }
162
- if (stabilize.state.value === STATE_NONE) {
163
- root(() => signal.set(stabilize.state, STATE_DIRTY));
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.');
164
158
  }
165
159
  }
166
160
  function unlink(link) {
@@ -182,22 +176,22 @@ function unlink(link) {
182
176
  }
183
177
  return nextDep;
184
178
  }
185
- function update(el) {
186
- if (el.state & STATE_CHECK) {
187
- for (let d = el.deps; d; d = d.nextDep) {
188
- let dep = d.dep;
179
+ function update(computed) {
180
+ if (computed.state & STATE_CHECK) {
181
+ for (let link = computed.deps; link; link = link.nextDep) {
182
+ let dep = link.dep;
189
183
  if ('fn' in dep) {
190
184
  update(dep);
191
185
  }
192
- if (el.state & STATE_DIRTY) {
186
+ if (computed.state & STATE_DIRTY) {
193
187
  break;
194
188
  }
195
189
  }
196
190
  }
197
- if (el.state & STATE_DIRTY) {
198
- recompute(el, true);
191
+ if (computed.state & STATE_DIRTY) {
192
+ recompute(computed, true);
199
193
  }
200
- el.state = STATE_NONE;
194
+ computed.state = STATE_NONE;
201
195
  }
202
196
  const computed = (fn) => {
203
197
  let self = {
@@ -231,25 +225,22 @@ const computed = (fn) => {
231
225
  }
232
226
  return self;
233
227
  };
234
- const dispose = (el) => {
235
- deleteFromHeap(el);
236
- let dep = el.deps;
228
+ const dispose = (computed) => {
229
+ deleteFromHeap(computed);
230
+ let dep = computed.deps;
237
231
  while (dep !== null) {
238
232
  dep = unlink(dep);
239
233
  }
240
- el.deps = null;
241
- cleanup(el);
234
+ computed.deps = null;
235
+ cleanup(computed);
242
236
  };
243
237
  const isComputed = (value) => {
244
238
  return isObject(value) && REACTIVE in value && 'fn' in value;
245
239
  };
246
- const isReactive = (value) => {
247
- return isObject(value) && REACTIVE in value;
248
- };
249
240
  const isSignal = (value) => {
250
241
  return isObject(value) && REACTIVE in value && 'fn' in value === false;
251
242
  };
252
- const oncleanup = (fn) => {
243
+ const onCleanup = (fn) => {
253
244
  if (!observer) {
254
245
  return fn;
255
246
  }
@@ -265,22 +256,29 @@ const oncleanup = (fn) => {
265
256
  }
266
257
  return fn;
267
258
  };
268
- const read = (el) => {
259
+ const read = (node) => {
269
260
  if (observer) {
270
- link(el, observer);
271
- if ('fn' in el) {
272
- let height = el.height;
261
+ link(node, observer);
262
+ if ('fn' in node) {
263
+ let height = node.height;
273
264
  if (height >= observer.height) {
274
265
  observer.height = height + 1;
275
266
  }
276
- if (height >= minDirty ||
277
- el.state & (STATE_DIRTY | STATE_CHECK)) {
278
- markHeap();
279
- update(el);
267
+ if (height >= iHeap ||
268
+ node.state & (STATE_DIRTY | STATE_CHECK)) {
269
+ if (!notifiedHeap) {
270
+ notifiedHeap = true;
271
+ for (let i = 0; i <= nHeap; i++) {
272
+ for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
273
+ notify(computed);
274
+ }
275
+ }
276
+ }
277
+ update(node);
280
278
  }
281
279
  }
282
280
  }
283
- return el.value;
281
+ return node.value;
284
282
  };
285
283
  const root = (fn) => {
286
284
  let o = observer;
@@ -297,29 +295,36 @@ const signal = (value) => {
297
295
  value,
298
296
  };
299
297
  };
300
- signal.set = (el, v) => {
301
- if (el.value === v) {
298
+ signal.set = (signal, value) => {
299
+ if (signal.value === value) {
302
300
  return;
303
301
  }
304
- el.value = v;
305
- for (let link = el.subs; link !== null; link = link.nextSub) {
306
- markedHeap = false;
302
+ notifiedHeap = false;
303
+ signal.value = value;
304
+ for (let link = signal.subs; link !== null; link = link.nextSub) {
307
305
  insertIntoHeap(link.sub);
308
306
  }
309
307
  };
310
308
  const stabilize = () => {
311
309
  root(() => {
312
- for (minDirty = 0; minDirty <= maxDirty; minDirty++) {
313
- let el = dirtyHeap[minDirty];
314
- dirtyHeap[minDirty] = undefined;
315
- while (el !== undefined) {
316
- let next = el.nextHeap;
317
- recompute(el, false);
318
- el = next;
310
+ for (iHeap = 0; iHeap <= nHeap; iHeap++) {
311
+ let computed = heap[iHeap];
312
+ heap[iHeap] = undefined;
313
+ while (computed !== undefined) {
314
+ let next = computed.nextHeap;
315
+ recompute(computed, false);
316
+ computed = next;
319
317
  }
320
318
  }
321
- signal.set(stabilize.state, STATE_NONE);
319
+ scheduled = false;
322
320
  });
323
321
  };
324
- stabilize.state = signal(STATE_NONE);
325
- export { computed, dispose, isComputed, isReactive, isSignal, oncleanup, read, root, signal, stabilize };
322
+ defineProperty(stabilize, 'scheduler', {
323
+ get() {
324
+ return scheduler;
325
+ },
326
+ set(s) {
327
+ scheduler = s;
328
+ },
329
+ });
330
+ export { computed, dispose, 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 './signal.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> {
@@ -7,7 +7,7 @@ interface Computed<T> extends Signal<T> {
7
7
  cleanup: VoidFunction | VoidFunction[] | null;
8
8
  deps: Link | null;
9
9
  depsTail: Link | null;
10
- fn: (oc?: typeof oncleanup) => T;
10
+ fn: (oc?: typeof onCleanup) => T;
11
11
  height: number;
12
12
  nextHeap: Computed<unknown> | undefined;
13
13
  prevHeap: Computed<unknown>;
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.0",
15
+ "version": "0.8.1",
16
16
  "scripts": {
17
17
  "build": "tsc && tsc-alias",
18
18
  "-": "-"
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export { default as reactive } from './reactive';
2
- export { computed, dispose, oncleanup, root, signal, stabilize } from './signal';
3
- export { default as scheduler } from './scheduler';
2
+ export * from './signal';
4
3
  export * from './types';
@@ -6,7 +6,7 @@ import { Signal } from '~/types';
6
6
  let { set } = signal;
7
7
 
8
8
 
9
- class ReactivePromise<A extends unknown[], R extends Promise<unknown>> extends CustomFunction {
9
+ class ReactiveAsyncFunction<A extends unknown[], R extends Promise<unknown>> extends CustomFunction {
10
10
  private arguments: Signal<A | null>;
11
11
  private okay: Signal<boolean | null>;
12
12
  private response: Signal<Awaited<R> | null>;
@@ -63,5 +63,5 @@ class ReactivePromise<A extends unknown[], R extends Promise<unknown>> extends C
63
63
 
64
64
 
65
65
  export default <A extends unknown[], R extends Promise<unknown>>(fn: (...args: A) => R) => {
66
- return new ReactivePromise(fn);
66
+ return new ReactiveAsyncFunction(fn);
67
67
  };
@@ -1,38 +1,48 @@
1
- import { isArray, isObject, isPromise } from '@esportsplus/utilities';
1
+ import { isArray, isAsyncFunction, isFunction, isObject } from '@esportsplus/utilities';
2
+ import { computed } from '~/signal';
3
+ import { Computed } from '~/types';
2
4
  import array from './array';
5
+ import async from './async';
3
6
  import object from './object';
4
- import promise from './promise';
5
7
 
6
8
 
7
9
  type API<T> =
8
10
  T extends (...args: infer A) => Promise<infer R>
9
- ? ReturnType<typeof promise<A, Promise<R>>>
10
- : T extends Record<PropertyKey, unknown>
11
- ? ReturnType<typeof object<T>>
12
- : T extends unknown[]
13
- ? ReturnType<typeof array<T>>
14
- : never;
11
+ ? 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;
15
19
 
16
- type Guard<T> =
20
+ type Input<T> =
17
21
  T extends (...args: unknown[]) => Promise<unknown>
18
22
  ? T
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;
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;
24
30
 
25
31
 
26
- export default <T>(data: Guard<T>): API<T> => {
27
- if (isArray(data)) {
28
- return array(data) as API<T>;
32
+ export default <T>(input: Input<T>): API<T> => {
33
+ if (isArray(input)) {
34
+ return array(input) as API<T>;
29
35
  }
30
- else if (isObject(data)) {
31
- return object(data) as API<T>;
36
+ else if (isAsyncFunction(input)) {
37
+ return async(input) as API<T>;
32
38
  }
33
- else if (isPromise(data)) {
34
- return promise(data) as API<T>;
39
+ else if (isFunction(input)) {
40
+ computed(input as Computed<T>['fn']);
41
+ return undefined as API<T>;
42
+ }
43
+ else if (isObject(input)) {
44
+ return object(input) as API<T>;
35
45
  }
36
46
 
37
- throw new Error(`@esportsplus/reactivity: 'reactive' received invalid input - ${JSON.stringify(data)}`);
47
+ throw new Error(`@esportsplus/reactivity: 'reactive' received invalid input - ${JSON.stringify(input)}`);
38
48
  };
@@ -3,7 +3,7 @@ import array, { ReactiveArray } from './array';
3
3
  import { computed, dispose, read, signal } from '~/signal';
4
4
  import { Computed, Infer, Signal } from '~/types';
5
5
  import { Disposable } from './disposable';
6
- import promise from './promise';
6
+ import async from './async';
7
7
 
8
8
 
9
9
  type API<T extends Record<PropertyKey, unknown>> = Prettify<{ [K in keyof T]: Infer<T[K]> }> & ReactiveObject<T>;
@@ -45,7 +45,7 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> extends Disposable
45
45
  });
46
46
  }
47
47
  else if (isAsyncFunction(value)) {
48
- let p = promise(value);
48
+ let p = async(value);
49
49
 
50
50
  defineProperty(this, key, {
51
51
  enumerable: true,
package/src/signal.ts CHANGED
@@ -1,13 +1,15 @@
1
- import { isArray, isObject } from '@esportsplus/utilities';
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';
3
3
  import { Computed, Link, Signal, } from './types';
4
4
 
5
5
 
6
- let dirtyHeap: (Computed<unknown> | undefined)[] = new Array(2000),
7
- maxDirty = 0,
8
- markedHeap = false,
9
- minDirty = 0,
10
- observer: Computed<unknown> | null = null;
6
+ let heap: (Computed<unknown> | undefined)[] = new Array(2000),
7
+ iHeap = 0,
8
+ nHeap = 0,
9
+ notifiedHeap = false,
10
+ observer: Computed<unknown> | null = null,
11
+ scheduled = false,
12
+ scheduler: ((task: VoidFunction) => void) | null = null;
11
13
 
12
14
 
13
15
  function cleanup<T>(node: Computed<T>): void {
@@ -15,80 +17,82 @@ function cleanup<T>(node: Computed<T>): void {
15
17
  return;
16
18
  }
17
19
 
18
- if (isArray(node.cleanup)) {
19
- for (let i = 0; i < node.cleanup.length; i++) {
20
- node.cleanup[i]();
20
+ let cleanup = node.cleanup;
21
+
22
+ if (isArray(cleanup)) {
23
+ for (let i = 0; i < cleanup.length; i++) {
24
+ cleanup[i]();
21
25
  }
22
26
  }
23
27
  else {
24
- node.cleanup();
28
+ cleanup();
25
29
  }
26
30
 
27
31
  node.cleanup = null;
28
32
  }
29
33
 
30
- function deleteFromHeap<T>(n: Computed<T>) {
31
- let state = n.state;
34
+ function deleteFromHeap<T>(computed: Computed<T>) {
35
+ let state = computed.state;
32
36
 
33
37
  if (!(state & STATE_IN_HEAP)) {
34
38
  return;
35
39
  }
36
40
 
37
- n.state = state & ~STATE_IN_HEAP;
41
+ computed.state = state & ~STATE_IN_HEAP;
38
42
 
39
- let height = n.height;
43
+ let height = computed.height;
40
44
 
41
- if (n.prevHeap === n) {
42
- dirtyHeap[height] = undefined;
45
+ if (computed.prevHeap === computed) {
46
+ heap[height] = undefined;
43
47
  }
44
48
  else {
45
- let next = n.nextHeap,
46
- dhh = dirtyHeap[height]!,
49
+ let next = computed.nextHeap,
50
+ dhh = heap[height]!,
47
51
  end = next ?? dhh;
48
52
 
49
- if (n === dhh) {
50
- dirtyHeap[height] = next;
53
+ if (computed === dhh) {
54
+ heap[height] = next;
51
55
  }
52
56
  else {
53
- n.prevHeap.nextHeap = next;
57
+ computed.prevHeap.nextHeap = next;
54
58
  }
55
59
 
56
- end.prevHeap = n.prevHeap;
60
+ end.prevHeap = computed.prevHeap;
57
61
  }
58
62
 
59
- n.nextHeap = undefined;
60
- n.prevHeap = n;
63
+ computed.nextHeap = undefined;
64
+ computed.prevHeap = computed;
61
65
  }
62
66
 
63
- function insertIntoHeap<T>(n: Computed<T>) {
64
- let state = n.state;
67
+ function insertIntoHeap<T>(computed: Computed<T>) {
68
+ let state = computed.state;
65
69
 
66
70
  if (state & STATE_IN_HEAP) {
67
71
  return;
68
72
  }
69
73
 
70
- n.state = state | STATE_IN_HEAP;
74
+ computed.state = state | STATE_IN_HEAP;
71
75
 
72
- let height = n.height,
73
- heapAtHeight = dirtyHeap[height];
76
+ let height = computed.height,
77
+ heapAtHeight = heap[height];
74
78
 
75
79
  if (heapAtHeight === undefined) {
76
- dirtyHeap[height] = n;
80
+ heap[height] = computed;
77
81
  }
78
82
  else {
79
83
  let tail = heapAtHeight.prevHeap;
80
84
 
81
- tail.nextHeap = n;
82
- n.prevHeap = tail;
83
- heapAtHeight.prevHeap = n;
85
+ tail.nextHeap = computed;
86
+ computed.prevHeap = tail;
87
+ heapAtHeight.prevHeap = computed;
84
88
  }
85
89
 
86
- if (height > maxDirty) {
87
- maxDirty = height;
90
+ if (height > nHeap) {
91
+ nHeap = height;
88
92
 
89
93
  // Simple auto adjust to avoid manual management within apps.
90
- if (height >= dirtyHeap.length) {
91
- dirtyHeap.length += 250;
94
+ if (height >= heap.length) {
95
+ heap.length += 250;
92
96
  }
93
97
  }
94
98
  }
@@ -138,65 +142,51 @@ function link<T>(dep: Signal<T> | Computed<T>, sub: Computed<T>) {
138
142
  }
139
143
  }
140
144
 
141
- function markHeap() {
142
- if (markedHeap) {
143
- return;
144
- }
145
-
146
- markedHeap = true;
147
-
148
- for (let i = 0; i <= maxDirty; i++) {
149
- for (let el = dirtyHeap[i]; el !== undefined; el = el.nextHeap) {
150
- markNode(el);
151
- }
152
- }
153
- }
154
-
155
- function markNode<T>(el: Computed<T>, newState = STATE_DIRTY) {
156
- let state = el.state;
145
+ function notify<T>(computed: Computed<T>, newState = STATE_DIRTY) {
146
+ let state = computed.state;
157
147
 
158
148
  if ((state & (STATE_CHECK | STATE_DIRTY)) >= newState) {
159
149
  return;
160
150
  }
161
151
 
162
- el.state = state | newState;
152
+ computed.state = state | newState;
163
153
 
164
- for (let link = el.subs; link !== null; link = link.nextSub) {
165
- markNode(link.sub, STATE_CHECK);
154
+ for (let link = computed.subs; link !== null; link = link.nextSub) {
155
+ notify(link.sub, STATE_CHECK);
166
156
  }
167
157
  }
168
158
 
169
- function recompute<T>(el: Computed<T>, del: boolean) {
159
+ function recompute<T>(computed: Computed<T>, del: boolean) {
170
160
  if (del) {
171
- deleteFromHeap(el);
161
+ deleteFromHeap(computed);
172
162
  }
173
163
  else {
174
- el.nextHeap = undefined;
175
- el.prevHeap = el;
164
+ computed.nextHeap = undefined;
165
+ computed.prevHeap = computed;
176
166
  }
177
167
 
178
- cleanup(el);
168
+ cleanup(computed);
179
169
 
180
170
  let o = observer,
181
171
  ok = true,
182
172
  value;
183
173
 
184
- observer = el;
185
- el.depsTail = null;
186
- el.state = STATE_RECOMPUTING;
174
+ observer = computed;
175
+ computed.depsTail = null;
176
+ computed.state = STATE_RECOMPUTING;
187
177
 
188
178
  try {
189
- value = el.fn(oncleanup);
179
+ value = computed.fn(onCleanup);
190
180
  }
191
181
  catch (e) {
192
182
  ok = false;
193
183
  }
194
184
 
195
185
  observer = o;
196
- el.state = STATE_NONE;
186
+ computed.state = STATE_NONE;
197
187
 
198
- let depsTail = el.depsTail as Link | null,
199
- toRemove = depsTail !== null ? depsTail.nextDep : el.deps;
188
+ let depsTail = computed.depsTail as Link | null,
189
+ toRemove = depsTail !== null ? depsTail.nextDep : computed.deps;
200
190
 
201
191
  if (toRemove !== null) {
202
192
  do {
@@ -208,15 +198,15 @@ function recompute<T>(el: Computed<T>, del: boolean) {
208
198
  depsTail.nextDep = null;
209
199
  }
210
200
  else {
211
- el.deps = null;
201
+ computed.deps = null;
212
202
  }
213
203
  }
214
204
 
215
- if (ok && value !== el.value) {
216
- el.value = value as T;
205
+ if (ok && value !== computed.value) {
206
+ computed.value = value as T;
217
207
 
218
- for (let s = el.subs; s !== null; s = s.nextSub) {
219
- let o = s.sub,
208
+ for (let c = computed.subs; c !== null; c = c.nextSub) {
209
+ let o = c.sub,
220
210
  state = o.state;
221
211
 
222
212
  if (state & STATE_CHECK) {
@@ -227,8 +217,12 @@ function recompute<T>(el: Computed<T>, del: boolean) {
227
217
  }
228
218
  }
229
219
 
230
- if (stabilize.state.value === STATE_NONE) {
231
- root(() => signal.set(stabilize.state, STATE_DIRTY));
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.');
232
226
  }
233
227
  }
234
228
 
@@ -260,26 +254,26 @@ function unlink(link: Link): Link | null {
260
254
  return nextDep;
261
255
  }
262
256
 
263
- function update<T>(el: Computed<T>): void {
264
- if (el.state & STATE_CHECK) {
265
- for (let d = el.deps; d; d = d.nextDep) {
266
- let dep = d.dep;
257
+ function update<T>(computed: Computed<T>): void {
258
+ if (computed.state & STATE_CHECK) {
259
+ for (let link = computed.deps; link; link = link.nextDep) {
260
+ let dep = link.dep;
267
261
 
268
262
  if ('fn' in dep) {
269
263
  update(dep);
270
264
  }
271
265
 
272
- if (el.state & STATE_DIRTY) {
266
+ if (computed.state & STATE_DIRTY) {
273
267
  break;
274
268
  }
275
269
  }
276
270
  }
277
271
 
278
- if (el.state & STATE_DIRTY) {
279
- recompute(el, true);
272
+ if (computed.state & STATE_DIRTY) {
273
+ recompute(computed, true);
280
274
  }
281
275
 
282
- el.state = STATE_NONE;
276
+ computed.state = STATE_NONE;
283
277
  }
284
278
 
285
279
 
@@ -320,33 +314,29 @@ const computed = <T>(fn: Computed<T>['fn']): Computed<T> => {
320
314
  return self;
321
315
  };
322
316
 
323
- const dispose = <T>(el: Computed<T>) => {
324
- deleteFromHeap(el);
317
+ const dispose = <T>(computed: Computed<T>) => {
318
+ deleteFromHeap(computed);
325
319
 
326
- let dep = el.deps;
320
+ let dep = computed.deps;
327
321
 
328
322
  while (dep !== null) {
329
323
  dep = unlink(dep);
330
324
  }
331
325
 
332
- el.deps = null;
326
+ computed.deps = null;
333
327
 
334
- cleanup(el);
335
- }
328
+ cleanup(computed);
329
+ };
336
330
 
337
331
  const isComputed = (value: unknown): value is Computed<unknown> => {
338
332
  return isObject(value) && REACTIVE in value && 'fn' in value;
339
333
  };
340
334
 
341
- const isReactive = (value: unknown): value is Computed<unknown> | Signal<unknown> => {
342
- return isObject(value) && REACTIVE in value;
343
- };
344
-
345
335
  const isSignal = (value: unknown): value is Signal<unknown> => {
346
336
  return isObject(value) && REACTIVE in value && 'fn' in value === false;
347
337
  };
348
338
 
349
- const oncleanup = (fn: VoidFunction): typeof fn => {
339
+ const onCleanup = (fn: VoidFunction): typeof fn => {
350
340
  if (!observer) {
351
341
  return fn;
352
342
  }
@@ -366,28 +356,37 @@ const oncleanup = (fn: VoidFunction): typeof fn => {
366
356
  return fn;
367
357
  };
368
358
 
369
- const read = <T>(el: Signal<T> | Computed<T>): T => {
359
+ const read = <T>(node: Signal<T> | Computed<T>): T => {
370
360
  if (observer) {
371
- link(el, observer);
361
+ link(node, observer);
372
362
 
373
- if ('fn' in el) {
374
- let height = el.height;
363
+ if ('fn' in node) {
364
+ let height = node.height;
375
365
 
376
366
  if (height >= observer.height) {
377
367
  observer.height = height + 1;
378
368
  }
379
369
 
380
370
  if (
381
- height >= minDirty ||
382
- el.state & (STATE_DIRTY | STATE_CHECK)
371
+ height >= iHeap ||
372
+ node.state & (STATE_DIRTY | STATE_CHECK)
383
373
  ) {
384
- markHeap();
385
- update(el);
374
+ if (!notifiedHeap) {
375
+ notifiedHeap = true;
376
+
377
+ for (let i = 0; i <= nHeap; i++) {
378
+ for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
379
+ notify(computed);
380
+ }
381
+ }
382
+ }
383
+
384
+ update(node);
386
385
  }
387
386
  }
388
387
  }
389
388
 
390
- return el.value;
389
+ return node.value;
391
390
  };
392
391
 
393
392
  const root = <T>(fn: () => T) => {
@@ -411,47 +410,54 @@ const signal = <T>(value: T): Signal<T> => {
411
410
  };
412
411
  };
413
412
 
414
- signal.set = <T>(el: Signal<T>, v: T) => {
415
- if (el.value === v) {
413
+ signal.set = <T>(signal: Signal<T>, value: T) => {
414
+ if (signal.value === value) {
416
415
  return;
417
416
  }
418
417
 
419
- el.value = v;
418
+ notifiedHeap = false;
419
+ signal.value = value;
420
420
 
421
- for (let link = el.subs; link !== null; link = link.nextSub) {
422
- markedHeap = false;
421
+ for (let link = signal.subs; link !== null; link = link.nextSub) {
423
422
  insertIntoHeap(link.sub);
424
423
  }
425
424
  };
426
425
 
427
426
  const stabilize = () => {
428
427
  root(() => {
429
- for (minDirty = 0; minDirty <= maxDirty; minDirty++) {
430
- let el = dirtyHeap[minDirty];
428
+ for (iHeap = 0; iHeap <= nHeap; iHeap++) {
429
+ let computed = heap[iHeap];
431
430
 
432
- dirtyHeap[minDirty] = undefined;
431
+ heap[iHeap] = undefined;
433
432
 
434
- while (el !== undefined) {
435
- let next = el.nextHeap;
433
+ while (computed !== undefined) {
434
+ let next = computed.nextHeap;
436
435
 
437
- recompute(el, false);
436
+ recompute(computed, false);
438
437
 
439
- el = next;
438
+ computed = next;
440
439
  }
441
440
  }
442
441
 
443
- signal.set(stabilize.state, STATE_NONE);
442
+ scheduled = false;
444
443
  });
445
444
  };
446
445
 
447
- stabilize.state = signal(STATE_NONE);
446
+ defineProperty(stabilize, 'scheduler', {
447
+ get() {
448
+ return scheduler;
449
+ },
450
+ set(s: typeof scheduler) {
451
+ scheduler = s;
452
+ },
453
+ });
448
454
 
449
455
 
450
456
  export {
451
457
  computed,
452
458
  dispose,
453
- isComputed, isReactive, isSignal,
454
- oncleanup,
459
+ isComputed, isSignal,
460
+ onCleanup,
455
461
  read, root,
456
462
  signal, stabilize
457
463
  };
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 './signal';
3
3
  import { ReactiveArray } from './reactive/array';
4
4
  import { ReactiveObject } from './reactive/object';
5
5
 
@@ -9,7 +9,7 @@ interface Computed<T> extends Signal<T> {
9
9
  cleanup: VoidFunction | VoidFunction[] | null;
10
10
  deps: Link | null;
11
11
  depsTail: Link | null;
12
- fn: (oc?: typeof oncleanup) => T;
12
+ fn: (oc?: typeof onCleanup) => T;
13
13
  height: number;
14
14
  nextHeap: Computed<unknown> | undefined;
15
15
  prevHeap: Computed<unknown>;
@@ -1,4 +0,0 @@
1
- declare const scheduler: (schedule: (task: VoidFunction) => void) => void;
2
- declare const state: import("./types.js").Signal<number>;
3
- export default scheduler;
4
- export { state };
@@ -1,17 +0,0 @@
1
- import { STATE_DIRTY, STATE_NONE } from './constants.js';
2
- import { computed, dispose, read, signal, stabilize } from './signal.js';
3
- let c = null;
4
- const scheduler = (schedule) => {
5
- if (c) {
6
- dispose(c);
7
- }
8
- c = computed(() => {
9
- if (read(state) !== STATE_DIRTY) {
10
- return;
11
- }
12
- schedule(stabilize);
13
- });
14
- };
15
- const state = signal(STATE_NONE);
16
- export default scheduler;
17
- export { state };
package/src/scheduler.ts DELETED
@@ -1,27 +0,0 @@
1
- import { STATE_DIRTY, STATE_NONE } from './constants';
2
- import { computed, dispose, read, signal, stabilize } from './signal';
3
- import { Computed } from './types';
4
-
5
-
6
- let c: Computed<void> | null = null;
7
-
8
-
9
- const scheduler = (schedule: (task: VoidFunction) => void) => {
10
- if (c) {
11
- dispose(c);
12
- }
13
-
14
- c = computed(() => {
15
- if (read(state) !== STATE_DIRTY) {
16
- return;
17
- }
18
-
19
- schedule(stabilize);
20
- });
21
- };
22
-
23
- const state = signal(STATE_NONE);
24
-
25
-
26
- export default scheduler;
27
- export { state };