@esportsplus/reactivity 0.1.9 → 0.1.10

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,82 @@
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
- this.dispatch('dispose', this);
51
- flush(this);
65
+ flush('dispose', this, DISPOSED, this.value);
52
66
  }
53
67
  on(event, listener) {
54
68
  if (this.updating) {
55
69
  listener.once = true;
56
70
  }
57
- if (this.listeners === null || !(event in this.listeners)) {
58
- this.listeners ??= {};
59
- this.listeners[event] = [listener];
71
+ if (this.listeners === null) {
72
+ this.listeners = { [event]: [listener] };
60
73
  }
61
74
  else {
62
75
  let listeners = this.listeners[event];
63
- if (listeners.indexOf(listener) === -1) {
76
+ if (listeners === undefined) {
77
+ this.listeners[event] = [listener];
78
+ }
79
+ else if (listeners.indexOf(listener) === -1) {
64
80
  let i = listeners.indexOf(null);
65
81
  if (i === -1) {
66
82
  listeners.push(listener);
@@ -75,32 +91,84 @@ class Signal {
75
91
  listener.once = true;
76
92
  this.on(event, listener);
77
93
  }
94
+ }
95
+ class Computed extends Core {
96
+ changed;
97
+ fn;
98
+ constructor(fn, options) {
99
+ super(DIRTY, undefined);
100
+ this.changed = options?.changed || changed;
101
+ this.fn = fn;
102
+ }
103
+ get type() {
104
+ return COMPUTED;
105
+ }
106
+ get() {
107
+ return read(this);
108
+ }
78
109
  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
- }
110
+ flush('reset', this, DIRTY, undefined);
111
+ }
112
+ }
113
+ class Effect extends Core {
114
+ fn;
115
+ task;
116
+ constructor(fn) {
117
+ super(DIRTY, null);
118
+ this.fn = fn;
119
+ this.task = () => read(this);
120
+ update(this);
121
+ }
122
+ get type() {
123
+ return EFFECT;
124
+ }
125
+ reset() {
126
+ flush('reset', this, DIRTY, null);
127
+ update(this);
128
+ }
129
+ }
130
+ class Root extends Core {
131
+ scheduler;
132
+ tracking;
133
+ constructor(scheduler, tracking) {
134
+ super(CLEAN, null);
135
+ this.scheduler = scheduler;
136
+ this.tracking = tracking;
137
+ }
138
+ get type() {
139
+ return ROOT;
140
+ }
141
+ }
142
+ class Signal extends Core {
143
+ changed;
144
+ constructor(data, options) {
145
+ super(CLEAN, data);
146
+ this.changed = options?.changed || changed;
147
+ }
148
+ get type() {
149
+ return SIGNAL;
150
+ }
151
+ get() {
152
+ return read(this);
153
+ }
154
+ reset() {
155
+ flush('reset', this, CLEAN, this.value);
156
+ }
157
+ set(value) {
158
+ return write(this, value);
92
159
  }
93
160
  }
94
161
  function changed(a, b) {
95
162
  return a !== b;
96
163
  }
97
- function flush(node) {
98
- if (node.sources !== null) {
99
- removeSourceObservers(node, 0);
100
- }
164
+ function flush(event, node, state, value) {
165
+ node.dispatch(event, node);
166
+ removeSourceObservers(node, 0);
101
167
  node.listeners = null;
102
168
  node.observers = null;
103
169
  node.sources = null;
170
+ node.state = state;
171
+ node.value = value;
104
172
  }
105
173
  function notify(nodes, state) {
106
174
  if (nodes === null) {
@@ -117,6 +185,28 @@ function notify(nodes, state) {
117
185
  }
118
186
  }
119
187
  }
188
+ function read(node) {
189
+ if (node.state === DISPOSED) {
190
+ return node.value;
191
+ }
192
+ if (observer !== null) {
193
+ if (observers === null) {
194
+ if (observer.sources !== null && observer.sources[index] == node) {
195
+ index++;
196
+ }
197
+ else {
198
+ observers = [node];
199
+ }
200
+ }
201
+ else {
202
+ observers.push(node);
203
+ }
204
+ }
205
+ if (node.type === COMPUTED || node.type === EFFECT) {
206
+ sync(node);
207
+ }
208
+ return node.value;
209
+ }
120
210
  function removeSourceObservers(node, start) {
121
211
  if (node.sources === null) {
122
212
  return;
@@ -154,8 +244,8 @@ function update(node) {
154
244
  try {
155
245
  node.dispatch('update');
156
246
  node.updating = true;
157
- let value = node.fn.call(node, node.value);
158
- node.updating = null;
247
+ let value = node.fn.call(node);
248
+ node.updating = false;
159
249
  if (observers) {
160
250
  removeSourceObservers(node, index);
161
251
  if (node.sources !== null && index > 0) {
@@ -197,13 +287,18 @@ function update(node) {
197
287
  }
198
288
  node.state = CLEAN;
199
289
  }
200
- const computed = (fn, options = {}) => {
201
- let node = new Signal(options.value, DIRTY, COMPUTED, options);
202
- node.fn = fn;
203
- return node;
290
+ function write(node, value) {
291
+ if (node.changed(node.value, value)) {
292
+ node.value = value;
293
+ notify(node.observers, DIRTY);
294
+ }
295
+ return value;
296
+ }
297
+ const computed = (fn, options) => {
298
+ return new Computed(fn, options);
204
299
  };
205
300
  const dispose = (dispose) => {
206
- if (dispose === undefined) {
301
+ if (dispose == null) {
207
302
  }
208
303
  else if (isArray(dispose)) {
209
304
  for (let i = 0, n = dispose.length; i < n; i++) {
@@ -215,46 +310,11 @@ const dispose = (dispose) => {
215
310
  }
216
311
  return dispose;
217
312
  };
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;
233
- };
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;
313
+ const effect = (fn) => {
314
+ return new Effect(fn);
255
315
  };
256
316
  const reset = (reset) => {
257
- if (reset === undefined) {
317
+ if (reset == null) {
258
318
  }
259
319
  else if (isArray(reset)) {
260
320
  for (let i = 0, n = reset.length; i < n; i++) {
@@ -266,31 +326,22 @@ const reset = (reset) => {
266
326
  }
267
327
  return reset;
268
328
  };
269
- function root(fn, properties) {
329
+ const root = (fn, scheduler) => {
270
330
  let o = observer, s = scope;
271
- if (properties === undefined) {
331
+ if (scheduler === undefined) {
272
332
  if (scope === null) {
273
333
  throw new Error('Reactivity: `root` cannot be created without a task scheduler');
274
334
  }
275
- properties = scope;
335
+ scheduler = scope.scheduler;
276
336
  }
277
337
  observer = null;
278
- scope = properties;
279
- let result = fn();
338
+ scope = new Root(scheduler, fn.length > 0);
339
+ let result = fn(scope);
280
340
  observer = o;
281
341
  scope = s;
282
342
  return result;
283
- }
284
- ;
285
- const signal = (data, options = {}) => {
286
- return new Signal(data, CLEAN, SIGNAL, options);
287
343
  };
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;
344
+ const signal = (value, options) => {
345
+ return new Signal(value, options);
294
346
  };
295
- export default Signal;
296
- export { computed, dispose, effect, read, reset, root, signal, write };
347
+ export { computed, dispose, effect, reset, root, signal, 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.10"
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, reset, root, signal } from './signal';
6
5
  export * from './types';
package/src/macro.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import CustomFunction from '@esportsplus/custom-function';
2
- import { computed, read } from './signal';
3
- import { Computed, Options } from './types';
2
+ import { Computed } from './signal';
3
+ import { Options } from './types';
4
4
 
5
5
 
6
6
  type Function<A extends unknown[], R> = Computed<(...args: A) => R>['fn'];
@@ -12,9 +12,9 @@ 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
- this.#factory = computed(fn, options);
17
+ this.#factory = new Computed(fn, options);
18
18
  }
19
19
 
20
20
 
@@ -1,4 +1,4 @@
1
- import { dispose, read, signal, write } from '~/signal';
1
+ import { dispose } from '~/signal';
2
2
  import { Listener, Object, Options, Signal } from '~/types';
3
3
  import { ReactiveObject } from './object';
4
4
 
@@ -40,7 +40,7 @@ class ReactiveArray<T> extends Array<T> {
40
40
 
41
41
  constructor(data: T[]) {
42
42
  super(...data);
43
- this.#signal = signal(false);
43
+ this.#signal = new Signal(false);
44
44
  }
45
45
 
46
46
 
@@ -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
 
@@ -152,7 +152,7 @@ class ReactiveArray<T> extends Array<T> {
152
152
  }
153
153
 
154
154
  track() {
155
- read(this.#signal);
155
+ this.#signal.get();
156
156
  }
157
157
 
158
158
  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,3 @@
1
- import { computed, read, signal, write } from '~/signal';
2
1
  import { Computed, Object, Options, Signal } from '~/types';
3
2
  import { defineProperty, isArray } from '~/utilities';
4
3
  import { ReactiveArray, ReactiveObjectArray } from './array';
@@ -18,12 +17,12 @@ class ReactiveObject<T extends Object> {
18
17
  let input = data[key];
19
18
 
20
19
  if (typeof input === 'function') {
21
- let node = nodes[key] = computed(input as Computed<unknown>['fn'], options);
20
+ let node = nodes[key] = new Computed(input as Computed<T>['fn'], options);
22
21
 
23
22
  defineProperty(this, key, {
24
23
  enumerable: true,
25
24
  get() {
26
- return read(node);
25
+ return node.get();
27
26
  }
28
27
  });
29
28
  }
@@ -48,15 +47,15 @@ class ReactiveObject<T extends Object> {
48
47
  });
49
48
  }
50
49
  else {
51
- let node = nodes[key] = signal<unknown>(input, options);
50
+ let node = nodes[key] = new Signal(input, options);
52
51
 
53
52
  defineProperty(this, key, {
54
53
  enumerable: true,
55
54
  get() {
56
- return read(node);
55
+ return node.get();
57
56
  },
58
57
  set(value) {
59
- write(node, value);
58
+ node.set(value);
60
59
  }
61
60
  });
62
61
  }
package/src/resource.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import CustomFunction from '@esportsplus/custom-function';
2
- import { read, signal, write } from './signal';
3
- import { Options, Signal } from './types';
2
+ import { Signal } from './signal';
3
+ import { Options } from './types';
4
4
 
5
5
 
6
6
  type Function<A extends unknown[], R extends Promise<unknown>> = (...args: A) => R;
@@ -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 = new Signal(undefined as Awaited<R>, options);
43
+ this.#input = new Signal<A | null>(null, options);
44
+ this.#ok = new 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