@esportsplus/reactivity 0.1.9 → 0.1.11

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/signal.js CHANGED
@@ -1,66 +1,87 @@
1
- import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, SIGNAL } from './constants';
1
+ import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, ROOT, SIGNAL } from './constants';
2
2
  import { isArray } from './utilities';
3
3
  let index = 0, observer = null, observers = null, scope = null;
4
- class Signal {
5
- changed = null;
6
- fn = null;
4
+ class Core {
7
5
  listeners = null;
8
6
  observers = null;
9
- root = null;
7
+ root;
10
8
  sources = null;
11
9
  state;
12
- task = null;
13
- type;
14
- updating = null;
10
+ updating = false;
15
11
  value;
16
- constructor(data, state, type, options = {}) {
17
- if (options.changed !== undefined) {
18
- this.changed = options.changed;
12
+ constructor(state, value) {
13
+ let root = null;
14
+ if (this.type !== ROOT) {
15
+ if (scope !== null) {
16
+ root = scope;
17
+ }
18
+ else if (observer !== null) {
19
+ root = observer.root;
20
+ }
21
+ if (root == null) {
22
+ if (this.type === EFFECT) {
23
+ throw new Error(`Reactivity: 'effect' cannot be created without a reactive root`);
24
+ }
25
+ }
26
+ else if (root.tracking) {
27
+ root.on('dispose', () => this.dispose());
28
+ }
19
29
  }
30
+ this.root = root;
20
31
  this.state = state;
21
- this.type = type;
22
- this.value = data;
32
+ this.value = value;
33
+ }
34
+ get type() {
35
+ throw new Error(`Reactivity: reactive primitives require 'type' getters`);
23
36
  }
24
37
  dispatch(event, data) {
25
- if (this.listeners === null || !(event in this.listeners)) {
38
+ if (this.listeners === null || this.listeners[event] === undefined) {
26
39
  return;
27
40
  }
28
- let listeners = this.listeners[event], value = this.value;
41
+ let listeners = this.listeners[event], parameter = {
42
+ data,
43
+ value: this.value
44
+ };
29
45
  for (let i = 0, n = listeners.length; i < n; i++) {
30
46
  let listener = listeners[i];
31
47
  if (listener === null) {
32
48
  continue;
33
49
  }
34
50
  try {
35
- listener(listener.length === 0 ? null : { data, value });
51
+ listener(parameter);
52
+ if (listener.once !== undefined) {
53
+ listeners[i] = null;
54
+ }
36
55
  }
37
56
  catch {
38
57
  listeners[i] = null;
39
58
  }
40
- if (listener.once !== undefined) {
41
- listeners[i] = null;
42
- }
43
59
  }
44
60
  }
45
61
  dispose() {
46
62
  if (this.state === DISPOSED) {
47
63
  return;
48
64
  }
49
- this.state = DISPOSED;
50
65
  this.dispatch('dispose', this);
51
- flush(this);
66
+ removeSourceObservers(this, 0);
67
+ this.listeners = null;
68
+ this.observers = null;
69
+ this.sources = null;
70
+ this.state = DISPOSED;
52
71
  }
53
72
  on(event, listener) {
54
73
  if (this.updating) {
55
74
  listener.once = true;
56
75
  }
57
- if (this.listeners === null || !(event in this.listeners)) {
58
- this.listeners ??= {};
59
- this.listeners[event] = [listener];
76
+ if (this.listeners === null) {
77
+ this.listeners = { [event]: [listener] };
60
78
  }
61
79
  else {
62
80
  let listeners = this.listeners[event];
63
- if (listeners.indexOf(listener) === -1) {
81
+ if (listeners === undefined) {
82
+ this.listeners[event] = [listener];
83
+ }
84
+ else if (listeners.indexOf(listener) === -1) {
64
85
  let i = listeners.indexOf(null);
65
86
  if (i === -1) {
66
87
  listeners.push(listener);
@@ -75,32 +96,65 @@ class Signal {
75
96
  listener.once = true;
76
97
  this.on(event, listener);
77
98
  }
78
- reset() {
79
- this.dispatch('reset', this);
80
- flush(this);
81
- if (this.type === COMPUTED) {
82
- this.state = DIRTY;
83
- this.value = undefined;
84
- }
85
- else if (this.type === EFFECT) {
86
- this.state = DIRTY;
87
- update(this);
88
- }
89
- else if (this.type === SIGNAL) {
90
- this.state = CLEAN;
91
- }
99
+ }
100
+ class Computed extends Core {
101
+ changed;
102
+ fn;
103
+ constructor(fn, options) {
104
+ super(DIRTY, undefined);
105
+ this.changed = options?.changed || changed;
106
+ this.fn = fn;
107
+ }
108
+ get type() {
109
+ return COMPUTED;
110
+ }
111
+ get() {
112
+ return read(this);
92
113
  }
93
114
  }
94
- function changed(a, b) {
95
- return a !== b;
115
+ class Effect extends Core {
116
+ fn;
117
+ task;
118
+ constructor(fn) {
119
+ super(DIRTY, null);
120
+ this.fn = fn;
121
+ this.task = () => read(this);
122
+ update(this);
123
+ }
124
+ get type() {
125
+ return EFFECT;
126
+ }
96
127
  }
97
- function flush(node) {
98
- if (node.sources !== null) {
99
- removeSourceObservers(node, 0);
128
+ class Root extends Core {
129
+ scheduler;
130
+ tracking;
131
+ constructor(scheduler, tracking) {
132
+ super(CLEAN, null);
133
+ this.scheduler = scheduler;
134
+ this.tracking = tracking;
135
+ }
136
+ get type() {
137
+ return ROOT;
100
138
  }
101
- node.listeners = null;
102
- node.observers = null;
103
- node.sources = null;
139
+ }
140
+ class Signal extends Core {
141
+ changed;
142
+ constructor(data, options) {
143
+ super(CLEAN, data);
144
+ this.changed = options?.changed || changed;
145
+ }
146
+ get type() {
147
+ return SIGNAL;
148
+ }
149
+ get() {
150
+ return read(this);
151
+ }
152
+ set(value) {
153
+ return write(this, value);
154
+ }
155
+ }
156
+ function changed(a, b) {
157
+ return a !== b;
104
158
  }
105
159
  function notify(nodes, state) {
106
160
  if (nodes === null) {
@@ -117,6 +171,28 @@ function notify(nodes, state) {
117
171
  }
118
172
  }
119
173
  }
174
+ function read(node) {
175
+ if (node.state === DISPOSED) {
176
+ return node.value;
177
+ }
178
+ if (observer !== null) {
179
+ if (observers === null) {
180
+ if (observer.sources !== null && observer.sources[index] == node) {
181
+ index++;
182
+ }
183
+ else {
184
+ observers = [node];
185
+ }
186
+ }
187
+ else {
188
+ observers.push(node);
189
+ }
190
+ }
191
+ if (node.type === COMPUTED || node.type === EFFECT) {
192
+ sync(node);
193
+ }
194
+ return node.value;
195
+ }
120
196
  function removeSourceObservers(node, start) {
121
197
  if (node.sources === null) {
122
198
  return;
@@ -154,8 +230,8 @@ function update(node) {
154
230
  try {
155
231
  node.dispatch('update');
156
232
  node.updating = true;
157
- let value = node.fn.call(node, node.value);
158
- node.updating = null;
233
+ let value = node.fn.call(node);
234
+ node.updating = false;
159
235
  if (observers) {
160
236
  removeSourceObservers(node, index);
161
237
  if (node.sources !== null && index > 0) {
@@ -197,13 +273,18 @@ function update(node) {
197
273
  }
198
274
  node.state = CLEAN;
199
275
  }
200
- const computed = (fn, options = {}) => {
201
- let node = new Signal(options.value, DIRTY, COMPUTED, options);
202
- node.fn = fn;
203
- return node;
276
+ function write(node, value) {
277
+ if (node.changed(node.value, value)) {
278
+ node.value = value;
279
+ notify(node.observers, DIRTY);
280
+ }
281
+ return value;
282
+ }
283
+ const computed = (fn, options) => {
284
+ return new Computed(fn, options);
204
285
  };
205
286
  const dispose = (dispose) => {
206
- if (dispose === undefined) {
287
+ if (dispose == null) {
207
288
  }
208
289
  else if (isArray(dispose)) {
209
290
  for (let i = 0, n = dispose.length; i < n; i++) {
@@ -215,82 +296,26 @@ const dispose = (dispose) => {
215
296
  }
216
297
  return dispose;
217
298
  };
218
- const effect = (fn, options = {}) => {
219
- let node = new Signal(void 0, DIRTY, EFFECT, options);
220
- if (scope !== null) {
221
- node.root = scope;
222
- }
223
- else if (observer !== null && observer.type === EFFECT && observer.root !== null) {
224
- node.root = observer.root;
225
- }
226
- else {
227
- throw new Error('Reactivity: `effects` cannot be created without a reactive root');
228
- }
229
- node.fn = fn;
230
- node.task = () => read(node);
231
- read(node);
232
- return node;
299
+ const effect = (fn) => {
300
+ return new Effect(fn);
233
301
  };
234
- const read = (node) => {
235
- if (node.state === DISPOSED) {
236
- return node.value;
237
- }
238
- if (observer !== null) {
239
- if (observers === null) {
240
- if (observer.sources !== null && observer.sources[index] == node) {
241
- index++;
242
- }
243
- else {
244
- observers = [node];
245
- }
246
- }
247
- else {
248
- observers.push(node);
249
- }
250
- }
251
- if (node.fn !== null) {
252
- sync(node);
253
- }
254
- return node.value;
255
- };
256
- const reset = (reset) => {
257
- if (reset === undefined) {
258
- }
259
- else if (isArray(reset)) {
260
- for (let i = 0, n = reset.length; i < n; i++) {
261
- reset[i].reset();
262
- }
263
- }
264
- else {
265
- reset.reset();
266
- }
267
- return reset;
268
- };
269
- function root(fn, properties) {
302
+ const root = (fn, scheduler) => {
270
303
  let o = observer, s = scope;
271
- if (properties === undefined) {
304
+ if (scheduler === undefined) {
272
305
  if (scope === null) {
273
306
  throw new Error('Reactivity: `root` cannot be created without a task scheduler');
274
307
  }
275
- properties = scope;
308
+ scheduler = scope.scheduler;
276
309
  }
277
310
  observer = null;
278
- scope = properties;
279
- let result = fn();
311
+ scope = new Root(scheduler, fn.length > 0);
312
+ let result = fn(scope);
280
313
  observer = o;
281
314
  scope = s;
282
315
  return result;
283
- }
284
- ;
285
- const signal = (data, options = {}) => {
286
- return new Signal(data, CLEAN, SIGNAL, options);
287
316
  };
288
- const write = (node, value) => {
289
- if ((node.changed === null ? changed : node.changed)(node.value, value)) {
290
- node.value = value;
291
- notify(node.observers, DIRTY);
292
- }
293
- return node.value;
317
+ const signal = (value, options) => {
318
+ return new Signal(value, options);
294
319
  };
295
- export default Signal;
296
- export { computed, dispose, effect, read, reset, root, signal, write };
320
+ export { computed, dispose, effect, root, signal };
321
+ export { Computed, Effect, Signal };
package/build/types.d.ts CHANGED
@@ -1,16 +1,7 @@
1
1
  import { Function, NeverAsync, Prettify } from '@esportsplus/typescript';
2
- import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, SIGNAL } from './constants';
3
- import Signal from './signal';
2
+ import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, ROOT, SIGNAL } from './constants';
3
+ import { Computed, Effect, Signal } from './signal';
4
4
  type Changed = (a: unknown, b: unknown) => boolean;
5
- type Computed<T> = {
6
- fn: NeverAsync<(previous: T) => T>;
7
- } & Omit<Signal<T>, 'fn'>;
8
- type Effect = {
9
- fn: NeverAsync<(node: Effect) => void>;
10
- root: Root;
11
- task: Function;
12
- value: void;
13
- } & Omit<Signal<void>, 'fn' | 'root' | 'task' | 'value'>;
14
5
  type Event = string;
15
6
  type Listener<D> = {
16
7
  once?: boolean;
@@ -22,12 +13,7 @@ type Listener<D> = {
22
13
  type Object = Record<PropertyKey, unknown>;
23
14
  type Options = {
24
15
  changed?: Changed;
25
- value?: unknown;
26
16
  };
27
- type Root = {
28
- scheduler: Scheduler;
29
- };
30
- type Scheduler = (fn: Function) => unknown;
31
17
  type State = typeof CHECK | typeof CLEAN | typeof DIRTY | typeof DISPOSED;
32
- type Type = typeof COMPUTED | typeof EFFECT | typeof SIGNAL;
33
- export { Changed, Computed, Effect, Event, Listener, Object, Options, NeverAsync, Prettify, Root, Scheduler, Signal, State, Type };
18
+ type Type = typeof COMPUTED | typeof EFFECT | typeof ROOT | typeof SIGNAL;
19
+ export { Changed, Computed, Effect, Event, Function, Listener, Object, Options, NeverAsync, Prettify, Signal, State, Type };
package/build/types.js CHANGED
@@ -1,2 +1,2 @@
1
- import Signal from './signal';
2
- export { Signal };
1
+ import { Computed, Effect, Signal } from './signal';
2
+ export { Computed, Effect, Signal };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "@esportsplus/custom-function": "^0.0.1"
5
5
  },
6
6
  "devDependencies": {
7
- "@esportsplus/typescript": "^0.0.11"
7
+ "@esportsplus/typescript": "^0.0.13"
8
8
  },
9
9
  "main": "build/index.js",
10
10
  "name": "@esportsplus/reactivity",
@@ -16,5 +16,5 @@
16
16
  "prepublishOnly": "npm run build"
17
17
  },
18
18
  "types": "build/index.d.ts",
19
- "version": "0.1.9"
19
+ "version": "0.1.11"
20
20
  }
package/src/constants.ts CHANGED
@@ -11,7 +11,9 @@ const COMPUTED = 0;
11
11
 
12
12
  const EFFECT = 1;
13
13
 
14
- const SIGNAL = 2;
14
+ const ROOT = 2;
15
15
 
16
+ const SIGNAL = 3;
16
17
 
17
- export { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, SIGNAL };
18
+
19
+ export { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, ROOT, SIGNAL };
package/src/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { default as macro } from './macro';
2
2
  export { default as resource } from './resource';
3
- export * as core from './signal';
4
- export { effect, root } from './signal';
5
3
  export { default as reactive } from './reactive';
4
+ export { computed, dispose, effect, root, signal } from './signal';
6
5
  export * from './types';
package/src/macro.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import CustomFunction from '@esportsplus/custom-function';
2
- import { computed, read } from './signal';
2
+ import { computed } from './signal';
3
3
  import { Computed, Options } from './types';
4
4
 
5
5
 
@@ -12,7 +12,7 @@ class Macro<A extends unknown[], R> extends CustomFunction {
12
12
 
13
13
  constructor(fn: Function<A,R>, options: Options = {}) {
14
14
  super((...args: A) => {
15
- return read(this.#factory)(...args);
15
+ return this.#factory.get()(...args);
16
16
  });
17
17
  this.#factory = computed(fn, options);
18
18
  }
@@ -21,10 +21,6 @@ class Macro<A extends unknown[], R> extends CustomFunction {
21
21
  dispose() {
22
22
  this.#factory.dispose();
23
23
  }
24
-
25
- reset() {
26
- this.#factory.reset();
27
- }
28
24
  }
29
25
 
30
26
 
@@ -1,4 +1,4 @@
1
- import { dispose, read, signal, write } from '~/signal';
1
+ import { dispose, signal } from '~/signal';
2
2
  import { Listener, Object, Options, Signal } from '~/types';
3
3
  import { ReactiveObject } from './object';
4
4
 
@@ -54,7 +54,7 @@ class ReactiveArray<T> extends Array<T> {
54
54
 
55
55
 
56
56
  private trigger() {
57
- write(this.#signal, !this.#signal.value);
57
+ this.#signal.set(!this.#signal.value);
58
58
  }
59
59
 
60
60
 
@@ -103,10 +103,6 @@ class ReactiveArray<T> extends Array<T> {
103
103
  return n;
104
104
  }
105
105
 
106
- reset() {
107
- this.#signal.reset();
108
- }
109
-
110
106
  reverse() {
111
107
  super.reverse();
112
108
 
@@ -152,7 +148,7 @@ class ReactiveArray<T> extends Array<T> {
152
148
  }
153
149
 
154
150
  track() {
155
- read(this.#signal);
151
+ this.#signal.get();
156
152
  }
157
153
 
158
154
  unshift(...items: T[]) {
@@ -31,7 +31,7 @@ type Node<T> =
31
31
  type Nodes<T extends Object> = { [K in keyof T]: Node<T[K]> };
32
32
 
33
33
 
34
- export default <T extends Object>(data: Guard<T>, options: Options = {}) => {
34
+ export default <T extends Object>(data: Guard<T>, options?: Options) => {
35
35
  return new ReactiveObject(data, options) as any as Prettify<
36
36
  { [K in keyof T]: Infer<T[K]> } & Omit<ReactiveObject<T>, 'nodes'> & { nodes: Nodes<T> }
37
37
  >;
@@ -1,4 +1,4 @@
1
- import { computed, read, signal, write } from '~/signal';
1
+ import { computed, signal } from '~/signal';
2
2
  import { Computed, Object, Options, Signal } from '~/types';
3
3
  import { defineProperty, isArray } from '~/utilities';
4
4
  import { ReactiveArray, ReactiveObjectArray } from './array';
@@ -18,12 +18,12 @@ class ReactiveObject<T extends Object> {
18
18
  let input = data[key];
19
19
 
20
20
  if (typeof input === 'function') {
21
- let node = nodes[key] = computed(input as Computed<unknown>['fn'], options);
21
+ let node = nodes[key] = computed(input as Computed<T>['fn'], options);
22
22
 
23
23
  defineProperty(this, key, {
24
24
  enumerable: true,
25
25
  get() {
26
- return read(node);
26
+ return node.get();
27
27
  }
28
28
  });
29
29
  }
@@ -48,15 +48,15 @@ class ReactiveObject<T extends Object> {
48
48
  });
49
49
  }
50
50
  else {
51
- let node = nodes[key] = signal<unknown>(input, options);
51
+ let node = nodes[key] = signal(input, options);
52
52
 
53
53
  defineProperty(this, key, {
54
54
  enumerable: true,
55
55
  get() {
56
- return read(node);
56
+ return node.get();
57
57
  },
58
58
  set(value) {
59
- write(node, value);
59
+ node.set(value);
60
60
  }
61
61
  });
62
62
  }
@@ -71,14 +71,6 @@ class ReactiveObject<T extends Object> {
71
71
  nodes[key].dispose();
72
72
  }
73
73
  }
74
-
75
- reset() {
76
- let nodes = this.nodes;
77
-
78
- for (let key in nodes) {
79
- nodes[key].reset();
80
- }
81
- }
82
74
  }
83
75
 
84
76
 
package/src/resource.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import CustomFunction from '@esportsplus/custom-function';
2
- import { read, signal, write } from './signal';
2
+ import { signal } from './signal';
3
3
  import { Options, Signal } from './types';
4
4
 
5
5
 
@@ -18,8 +18,8 @@ class Resource<A extends unknown[], R extends Promise<unknown>> extends CustomFu
18
18
  super((...args: A) => {
19
19
  this.stop = null;
20
20
 
21
- write(this.#input, args);
22
- write(this.#ok, null);
21
+ this.#input.set(args);
22
+ this.#ok.set(null);
23
23
 
24
24
  fn(...args)
25
25
  .then((value) => {
@@ -27,34 +27,34 @@ class Resource<A extends unknown[], R extends Promise<unknown>> extends CustomFu
27
27
  return;
28
28
  }
29
29
 
30
- write(this.#data, value);
31
- write(this.#ok, true);
30
+ this.#data.set(value as Awaited<R>);
31
+ this.#ok.set(true);
32
32
  })
33
33
  .catch(() => {
34
34
  if (this.stop === true) {
35
35
  return;
36
36
  }
37
37
 
38
- write(this.#data, undefined);
39
- write(this.#ok, false);
38
+ this.#data.set(undefined as Awaited<R>);
39
+ this.#ok.set(false);
40
40
  });
41
41
  });
42
- this.#data = signal(options.value as Awaited<R>, options),
43
- this.#input = signal<A | null>(null, options),
44
- this.#ok = signal<boolean | null>(null, options)
42
+ this.#data = signal(undefined as Awaited<R>, options);
43
+ this.#input = signal<A | null>(null, options);
44
+ this.#ok = signal<boolean | null>(null, options);
45
45
  }
46
46
 
47
47
 
48
48
  get data() {
49
- return read(this.#data);
49
+ return this.#data.get();
50
50
  }
51
51
 
52
52
  get input() {
53
- return read(this.#input);
53
+ return this.#input.get();
54
54
  }
55
55
 
56
56
  get ok() {
57
- return read(this.#ok);
57
+ return this.#ok.get();
58
58
  }
59
59
 
60
60
 
@@ -63,12 +63,6 @@ class Resource<A extends unknown[], R extends Promise<unknown>> extends CustomFu
63
63
  this.#input.dispose();
64
64
  this.#ok.dispose();
65
65
  }
66
-
67
- reset() {
68
- this.#data.reset();
69
- this.#input.reset();
70
- this.#ok.reset();
71
- }
72
66
  }
73
67
 
74
68