@esportsplus/reactivity 0.0.29 → 0.1.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.
Files changed (63) hide show
  1. package/build/bench.d.ts +1 -0
  2. package/build/bench.js +46 -0
  3. package/build/constants.d.ts +8 -0
  4. package/build/constants.js +8 -0
  5. package/build/context/node.d.ts +1 -1
  6. package/build/context/node.js +1 -1
  7. package/build/context/nodes.d.ts +1 -1
  8. package/build/context/nodes.js +1 -1
  9. package/build/effect.d.ts +2 -6
  10. package/build/effect.js +1 -2
  11. package/build/index.d.ts +2 -4
  12. package/build/index.js +2 -4
  13. package/build/macro.d.ts +10 -7
  14. package/build/macro.js +17 -5
  15. package/build/promise.d.ts +25 -5
  16. package/build/promise.js +37 -30
  17. package/build/reactive/array.d.ts +60 -0
  18. package/build/reactive/array.js +136 -0
  19. package/build/reactive/index.d.ts +11 -0
  20. package/build/reactive/index.js +4 -0
  21. package/build/reactive/object.d.ts +14 -0
  22. package/build/reactive/object.js +60 -0
  23. package/build/reactive/object2.d.ts +10 -0
  24. package/build/reactive/object2.js +65 -0
  25. package/build/reactive/types.d.ts +33 -0
  26. package/build/reactive/types.js +1 -0
  27. package/build/reactive-array.d.ts +1 -0
  28. package/build/reactive-array.js +106 -0
  29. package/build/reactive.js +2 -2
  30. package/build/resource.d.ts +16 -0
  31. package/build/resource.js +55 -0
  32. package/build/signal.d.ts +17 -12
  33. package/build/signal.js +71 -37
  34. package/build/testing/node.d.ts +13 -0
  35. package/build/testing/node.js +21 -0
  36. package/build/testing/nodes.d.ts +13 -0
  37. package/build/testing/nodes.js +33 -0
  38. package/build/testing/reactive.d.ts +0 -0
  39. package/build/testing/reactive.js +1 -0
  40. package/build/trigger.d.ts +15 -0
  41. package/build/trigger.js +30 -0
  42. package/build/types.d.ts +15 -17
  43. package/build/utilities.d.ts +3 -0
  44. package/build/utilities.js +3 -0
  45. package/package.json +5 -2
  46. package/src/constants.ts +17 -0
  47. package/src/index.ts +3 -5
  48. package/src/macro.ts +29 -9
  49. package/src/reactive/array.ts +219 -0
  50. package/src/reactive/index.ts +26 -0
  51. package/src/reactive/object.ts +93 -0
  52. package/src/resource.ts +79 -0
  53. package/src/signal.ts +91 -52
  54. package/src/types.ts +13 -19
  55. package/src/utilities.ts +6 -0
  56. package/tsconfig.json +1 -1
  57. package/src/context/index.ts +0 -5
  58. package/src/context/node.ts +0 -36
  59. package/src/context/nodes.ts +0 -52
  60. package/src/effect.ts +0 -5
  61. package/src/promise.ts +0 -48
  62. package/src/reactive.ts +0 -47
  63. package/src/symbols.ts +0 -29
package/src/signal.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, DISPOSE, EFFECT, RESET, SIGNAL, UPDATE } from '~/symbols';
2
- import { Changed, Computed, Context, Effect, Event, Listener, Options, Root, Scheduler, State, Type } from '~/types';
1
+ import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, SIGNAL } from './constants';
2
+ import { Changed, Computed, Effect, Event, Listener, Options, Root, Scheduler, State, Type } from './types';
3
+ import { isArray } from './utilities';
4
+
3
5
 
4
6
 
5
7
  let index = 0,
@@ -11,7 +13,7 @@ let index = 0,
11
13
  class Signal<T> {
12
14
  changed: Changed | null = null;
13
15
  fn: Computed<T>['fn'] | null = null;
14
- listeners: Record<symbol, (Listener | null)[]> | null = null;
16
+ listeners: Record<Event, (Listener<any> | null)[]> | null = null;
15
17
  observers: Signal<T>[] | null = null;
16
18
  root: Root | null = null;
17
19
  sources: Signal<T>[] | null = null;
@@ -23,7 +25,7 @@ class Signal<T> {
23
25
 
24
26
 
25
27
  constructor(data: T, state: Signal<T>['state'], type: Signal<T>['type'], options: Options = {}) {
26
- if (options?.changed) {
28
+ if (options.changed !== undefined) {
27
29
  this.changed = options.changed;
28
30
  }
29
31
 
@@ -33,6 +35,35 @@ class Signal<T> {
33
35
  }
34
36
 
35
37
 
38
+ dispatch<D>(event: Event, data?: D) {
39
+ if (this.listeners === null || !(event in this.listeners)) {
40
+ return;
41
+ }
42
+
43
+ let listeners = this.listeners[event],
44
+ value = this.value;
45
+
46
+ for (let i = 0, n = listeners.length; i < n; i++) {
47
+ let listener = listeners[i];
48
+
49
+ if (listener === null) {
50
+ continue;
51
+ }
52
+
53
+ try {
54
+ // @ts-ignore
55
+ listener( listener.length === 0 ? null : { data, value } );
56
+ }
57
+ catch {
58
+ listeners[i] = null;
59
+ }
60
+
61
+ if (listener.once !== undefined) {
62
+ listeners[i] = null;
63
+ }
64
+ }
65
+ }
66
+
36
67
  dispose() {
37
68
  if (this.state === DISPOSED) {
38
69
  return;
@@ -40,16 +71,16 @@ class Signal<T> {
40
71
 
41
72
  this.state = DISPOSED;
42
73
 
43
- dispatch(DISPOSE, this);
74
+ this.dispatch('dispose', this);
44
75
  flush(this);
45
76
  }
46
77
 
47
- on(event: Event, listener: Listener) {
78
+ on(event: Event, listener: Listener<any>) {
48
79
  if (this.updating) {
49
80
  listener.once = true;
50
81
  }
51
82
 
52
- if (!this.listeners?.[event]) {
83
+ if (this.listeners === null || !(event in this.listeners)) {
53
84
  this.listeners ??= {};
54
85
  this.listeners[event] = [listener];
55
86
  }
@@ -69,13 +100,13 @@ class Signal<T> {
69
100
  }
70
101
  }
71
102
 
72
- once(event: Event, listener: Listener) {
103
+ once(event: Event, listener: Listener<any>) {
73
104
  listener.once = true;
74
105
  this.on(event, listener);
75
106
  }
76
107
 
77
108
  reset() {
78
- dispatch(RESET, this);
109
+ this.dispatch('reset', this);
79
110
  flush(this);
80
111
 
81
112
  if (this.type === COMPUTED) {
@@ -97,31 +128,8 @@ function changed(a: unknown, b: unknown) {
97
128
  return a !== b;
98
129
  }
99
130
 
100
- function dispatch<T>(event: Event, node: Signal<T>) {
101
- if (!node.listeners?.[event]) {
102
- return;
103
- }
104
-
105
- let listeners = node.listeners[event],
106
- value = node.value;
107
-
108
- for (let i = 0, n = listeners.length; i < n; i++) {
109
- let listener = listeners[i];
110
-
111
- if (!listener) {
112
- continue;
113
- }
114
-
115
- listener(value);
116
-
117
- if (listener?.once) {
118
- listeners[i] = null;
119
- }
120
- }
121
- }
122
-
123
131
  function flush<T>(node: Signal<T>) {
124
- if (node.sources) {
132
+ if (node.sources !== null) {
125
133
  removeSourceObservers(node, 0);
126
134
  }
127
135
 
@@ -131,7 +139,7 @@ function flush<T>(node: Signal<T>) {
131
139
  }
132
140
 
133
141
  function notify<T>(nodes: Signal<T>[] | null, state: typeof CHECK | typeof DIRTY) {
134
- if (!nodes) {
142
+ if (nodes === null) {
135
143
  return;
136
144
  }
137
145
 
@@ -150,14 +158,14 @@ function notify<T>(nodes: Signal<T>[] | null, state: typeof CHECK | typeof DIRTY
150
158
  }
151
159
 
152
160
  function removeSourceObservers<T>(node: Signal<T>, start: number) {
153
- if (!node.sources) {
161
+ if (node.sources === null) {
154
162
  return;
155
163
  }
156
164
 
157
165
  for (let i = start, n = node.sources.length; i < n; i++) {
158
166
  let source = node.sources[i];
159
167
 
160
- if (!source?.observers) {
168
+ if (source.observers === null) {
161
169
  continue;
162
170
  }
163
171
 
@@ -167,7 +175,7 @@ function removeSourceObservers<T>(node: Signal<T>, start: number) {
167
175
  }
168
176
 
169
177
  function sync<T>(node: Signal<T>) {
170
- if (node.state === CHECK && node.sources) {
178
+ if (node.state === CHECK && node.sources !== null) {
171
179
  for (let i = 0, n = node.sources.length; i < n; i++) {
172
180
  sync(node.sources[i]);
173
181
 
@@ -198,18 +206,17 @@ function update<T>(node: Signal<T>) {
198
206
  observers = null as typeof observers;
199
207
 
200
208
  try {
201
- dispatch(UPDATE, node);
202
-
209
+ node.dispatch('update');
203
210
  node.updating = true;
204
211
 
205
- let value = node.fn!.call(node as Context, node?.value);
212
+ let value = node.fn!.call(node, node.value);
206
213
 
207
214
  node.updating = null;
208
215
 
209
216
  if (observers) {
210
217
  removeSourceObservers(node, index);
211
218
 
212
- if (node.sources && index > 0) {
219
+ if (node.sources !== null && index > 0) {
213
220
  node.sources.length = index + observers.length;
214
221
 
215
222
  for (let i = 0, n = observers.length; i < n; i++) {
@@ -231,7 +238,7 @@ function update<T>(node: Signal<T>) {
231
238
  }
232
239
  }
233
240
  }
234
- else if (node.sources && index < node.sources.length) {
241
+ else if (node.sources !== null && index < node.sources.length) {
235
242
  removeSourceObservers(node, index);
236
243
  node.sources.length = index;
237
244
  }
@@ -255,15 +262,30 @@ function update<T>(node: Signal<T>) {
255
262
  }
256
263
 
257
264
 
258
- const computed = <T>(fn: Computed<T>['fn'], options: Options & { value?: unknown } = {}) => {
259
- let node = new Signal(options?.value as any, DIRTY, COMPUTED, options);
265
+ const computed = <T>(fn: Computed<T>['fn'], options: Options = {}) => {
266
+ let node = new Signal(options.value as any, DIRTY, COMPUTED, options);
260
267
 
261
268
  node.fn = fn;
262
269
 
263
270
  return node as Computed<T>;
264
271
  };
265
272
 
266
- const effect = <T>(fn: Effect<T>['fn'], options: Options = {}) => {
273
+ const dispose = <T extends { dispose: () => void }>(dispose?: T[] | T) => {
274
+ if (dispose === undefined) {
275
+ }
276
+ else if (isArray(dispose)) {
277
+ for (let i = 0, n = dispose.length; i < n; i++) {
278
+ dispose[i].dispose();
279
+ }
280
+ }
281
+ else {
282
+ dispose.dispose();
283
+ }
284
+
285
+ return dispose;
286
+ };
287
+
288
+ const effect = <T>(fn: Effect<T>['fn'], options: Omit<Options, 'value'> = {}) => {
267
289
  if (!scope) {
268
290
  throw new Error('Reactivity: `effects` cannot be created without a reactive root');
269
291
  }
@@ -286,7 +308,7 @@ const read = <T>(node: Signal<T>): typeof node['value'] => {
286
308
 
287
309
  if (observer) {
288
310
  if (!observers) {
289
- if (observer?.sources?.[index] == node) {
311
+ if (observer.sources !== null && observer.sources[index] == node) {
290
312
  index++;
291
313
  }
292
314
  else {
@@ -305,14 +327,31 @@ const read = <T>(node: Signal<T>): typeof node['value'] => {
305
327
  return node.value;
306
328
  };
307
329
 
330
+ const reset = <T extends { reset: () => void }>(reset?: T[] | T) => {
331
+ if (reset === undefined) {
332
+ }
333
+ else if (isArray(reset)) {
334
+ for (let i = 0, n = reset.length; i < n; i++) {
335
+ reset[i].reset();
336
+ }
337
+ }
338
+ else {
339
+ reset.reset();
340
+ }
341
+
342
+ return reset;
343
+ };
344
+
308
345
  const root = <T>(fn: () => T, properties: { scheduler?: Scheduler } = {}) => {
309
346
  let o = observer,
310
347
  s = scope;
311
348
 
312
- properties.scheduler = properties?.scheduler || scope?.scheduler;
349
+ if (properties.scheduler === undefined) {
350
+ properties.scheduler = scope?.scheduler;
313
351
 
314
- if (!properties.scheduler) {
315
- throw new Error('Reactivity: `root` cannot be created without a task scheduler');
352
+ if (properties.scheduler === undefined) {
353
+ throw new Error('Reactivity: `root` cannot be created without a task scheduler');
354
+ }
316
355
  }
317
356
 
318
357
  observer = null;
@@ -326,12 +365,12 @@ const root = <T>(fn: () => T, properties: { scheduler?: Scheduler } = {}) => {
326
365
  return result;
327
366
  };
328
367
 
329
- const signal = <T>(data: T, options: Options = {}) => {
368
+ const signal = <T>(data: T, options: Omit<Options, 'value'> = {}) => {
330
369
  return new Signal(data, CLEAN, SIGNAL, options);
331
370
  };
332
371
 
333
372
  const write = <T>(node: Signal<T>, value: unknown) => {
334
- if ((node?.changed || changed)(node.value, value)) {
373
+ if ((node.changed === null ? changed : node.changed)(node.value, value)) {
335
374
  node.value = value as T;
336
375
  notify(node.observers, DIRTY);
337
376
  }
@@ -341,4 +380,4 @@ const write = <T>(node: Signal<T>, value: unknown) => {
341
380
 
342
381
 
343
382
  export default Signal;
344
- export { computed, effect, read, root, signal, write };
383
+ export { computed, dispose, effect, read, reset, root, signal, write };
package/src/types.ts CHANGED
@@ -1,47 +1,41 @@
1
- import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, SIGNAL } from './symbols';
1
+ import { Prettify } from '@esportsplus/typescript'
2
+ import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, SIGNAL } from './constants';
2
3
  import Signal from './signal';
3
4
 
4
5
 
5
6
  type Changed = (a: unknown, b: unknown) => boolean;
6
7
 
7
8
  type Computed<T> = {
8
- fn: T extends Promise<unknown> ? never : ((this: Context, previous: T) => T);
9
+ fn: T extends Promise<unknown> ? never : ((previous: T) => T);
9
10
  value: ReturnType<Computed<T>['fn']>;
10
11
  } & Omit<Signal<T>, 'fn' | 'value'>;
11
12
 
12
- type Context = {
13
- dispose(): void;
14
- on(event: Event, listener: Listener): void;
15
- once(event: Event, listener: Listener): void;
16
- reset(): void;
17
- };
18
-
19
13
  type Effect<T> = {
20
- fn: (this: Context, previous: T) => T;
14
+ fn: (node: Effect<T>) => void;
21
15
  root: NonNullable<Signal<T>['root']>;
22
16
  task: NonNullable<Signal<T>['task']>
23
- } & Omit<Computed<T>, 'fn' | 'root' | 'task'>;
17
+ value: void;
18
+ } & Omit<Signal<T>, 'fn' | 'root' | 'task' | 'value'>;
24
19
 
25
- type Event = symbol;
20
+ type Event = string;
26
21
 
27
- type Listener = {
22
+ type Listener<D> = {
28
23
  once?: boolean;
29
24
 
30
- <T>(value: T): void;
25
+ <V>(event: { data?: D, value: V }): void;
31
26
  };
32
27
 
28
+ type Object = Record<PropertyKey, unknown>;
29
+
33
30
  type Options = {
34
31
  changed?: Changed;
32
+ value?: unknown;
35
33
  };
36
34
 
37
35
  type Root = {
38
36
  scheduler: Scheduler
39
37
  };
40
38
 
41
- type Prettify<T> = {
42
- [K in keyof T]: T[K];
43
- } & {};
44
-
45
39
  type Scheduler = (fn: (...args: unknown[]) => Promise<unknown> | unknown) => unknown;
46
40
 
47
41
  type State = typeof CHECK | typeof CLEAN | typeof DIRTY | typeof DISPOSED;
@@ -49,4 +43,4 @@ type State = typeof CHECK | typeof CLEAN | typeof DIRTY | typeof DISPOSED;
49
43
  type Type = typeof COMPUTED | typeof EFFECT | typeof SIGNAL;
50
44
 
51
45
 
52
- export { Changed, Computed, Context, Effect, Event, Listener, Options, Prettify, Root, Scheduler, Signal, State, Type };
46
+ export { Changed, Computed, Effect, Event, Listener, Object, Options, Prettify, Root, Scheduler, Signal, State, Type };
@@ -0,0 +1,6 @@
1
+ const { isArray } = Array;
2
+
3
+ const { defineProperty } = Object;
4
+
5
+
6
+ export { defineProperty, isArray };
package/tsconfig.json CHANGED
@@ -5,6 +5,6 @@
5
5
  "outDir": "build",
6
6
  },
7
7
  "exclude": ["node_modules"],
8
- "extends": "@esportsplus/rspack/tsconfig.base.json",
8
+ "extends": "@esportsplus/typescript/tsconfig.base.json",
9
9
  "include": ["src"]
10
10
  }
@@ -1,5 +0,0 @@
1
- import node from './node';
2
- import nodes from './nodes';
3
-
4
-
5
- export default { node, nodes };
@@ -1,36 +0,0 @@
1
- import { NODE } from '~/symbols';
2
- import { Context, Event, Listener, Prettify, Signal } from '~/types';
3
-
4
-
5
- type Internals = {
6
- [NODE]: Signal<any>;
7
- };
8
-
9
-
10
- function dispose(this: Internals) {
11
- this[NODE].dispose();
12
- }
13
-
14
- function on(this: Internals, event: Event, listener: Listener) {
15
- this[NODE].on(event,listener);
16
- }
17
-
18
- function once(this: Internals, event: Event, listener: Listener) {
19
- this[NODE].once(event, listener);
20
- }
21
-
22
- function reset(this: Internals) {
23
- this[NODE].reset();
24
- }
25
-
26
-
27
- export default <T>(host: T & Partial<Context>, node: Internals[typeof NODE]) => {
28
- (host as unknown as Internals)[NODE] = node;
29
-
30
- host.dispose = dispose;
31
- host.on = on;
32
- host.once = once;
33
- host.reset = reset;
34
-
35
- return host as Prettify< Required<typeof host> >;
36
- };
@@ -1,52 +0,0 @@
1
- import { NODES } from '~/symbols';
2
- import { Context, Event, Listener, Prettify, Signal } from '~/types';
3
-
4
-
5
- type Internals = {
6
- [NODES]: Record<PropertyKey, Signal<any>> ;
7
- };
8
-
9
-
10
- function dispose(this: Internals) {
11
- let nodes = this[NODES];
12
-
13
- for (let key in nodes) {
14
- nodes[key].dispose();
15
- }
16
- }
17
-
18
- function on(this: Internals, event: Event, listener: Listener) {
19
- let nodes = this[NODES];
20
-
21
- for (let key in nodes) {
22
- nodes[key].on(event, listener);
23
- }
24
- }
25
-
26
- function once(this: Internals, event: Event, listener: Listener) {
27
- let nodes = this[NODES];
28
-
29
- for (let key in nodes) {
30
- nodes[key].once(event, listener);
31
- }
32
- }
33
-
34
- function reset(this: Internals) {
35
- let nodes = this[NODES];
36
-
37
- for (let key in nodes) {
38
- nodes[key].reset();
39
- }
40
- }
41
-
42
-
43
- export default <T>(host: T & Partial<Context>, nodes: Internals[typeof NODES]) => {
44
- (host as unknown as Internals)[NODES] = nodes;
45
-
46
- host.dispose = dispose;
47
- host.on = on;
48
- host.once = once;
49
- host.reset = reset;
50
-
51
- return host as Prettify< Required<typeof host> >;
52
- };
package/src/effect.ts DELETED
@@ -1,5 +0,0 @@
1
- import { effect } from './signal';
2
- import context from './context';
3
-
4
-
5
- export default (...args: Parameters<typeof effect>) => context.node({}, effect(...args));
package/src/promise.ts DELETED
@@ -1,48 +0,0 @@
1
- import { computed, read, root, signal, write } from './signal';
2
- import context from './context';
3
-
4
-
5
- // TODO:
6
- // - Add status value
7
- // - Add reject/stop method
8
- export default (fn: <A, R extends Promise<any>>(...args: A[]) => R, options: Parameters<typeof computed>[1] = {}) => {
9
- let input: unknown,
10
- nodes = {
11
- data: signal(options?.value, options),
12
- status: signal(undefined, options)
13
- };
14
-
15
- function host(this: typeof host, ...args: Parameters<typeof fn>) {
16
- input = args;
17
-
18
- root(() => {
19
- fn(...args)
20
- .then(<T>(value: T) => {
21
- write(nodes.data, value);
22
- })
23
- .catch(() => {
24
- write(nodes.data, undefined);
25
- });
26
- });
27
- }
28
-
29
- Object.defineProperties(host, {
30
- data: {
31
- get() {
32
- return read(nodes.data);
33
- }
34
- },
35
- input: {
36
- get() {
37
- return input;
38
- }
39
- },
40
- status: {
41
- get() {
42
- return read(nodes.status);
43
- }
44
- }
45
- });
46
-
47
- return context.nodes(host, nodes);
48
- };
package/src/reactive.ts DELETED
@@ -1,47 +0,0 @@
1
- import { computed, read, signal, write } from './signal';
2
- import { Context, Signal } from './types';
3
- import context from './context';
4
-
5
-
6
- type Infer<T> =
7
- T extends (...args: unknown[]) => unknown
8
- ? ReturnType<T>
9
- : T extends Record<PropertyKey, unknown>
10
- ? { [K in keyof T]: T[K] }
11
- : T;
12
-
13
- type Never = { [K in keyof Context]?: never };
14
-
15
- type Options = Parameters<typeof computed>[1] | Parameters<typeof signal>[1];
16
-
17
-
18
- export default <T extends Record<PropertyKey, unknown>>(data: T & Never, options: Options = {}) => {
19
- let host = {},
20
- nodes: Record<PropertyKey, Signal<unknown>> = {};
21
-
22
- for (let key in (data as T)) {
23
- if (typeof data[key] === 'function') {
24
- nodes[key] = computed(data[key] as Parameters<typeof computed>[0], options);
25
-
26
- Object.defineProperty(host, key, {
27
- get() {
28
- return read(nodes[key]);
29
- }
30
- });
31
- }
32
- else {
33
- nodes[key] = signal(data[key], options);
34
-
35
- Object.defineProperty(host, key, {
36
- get() {
37
- return read(nodes[key]);
38
- },
39
- set(data) {
40
- write(nodes[key], data);
41
- }
42
- });
43
- }
44
- }
45
-
46
- return context.nodes(host as Infer<T> & Context, nodes);
47
- };
package/src/symbols.ts DELETED
@@ -1,29 +0,0 @@
1
- const CLEAN = 0;
2
-
3
- const CHECK = 1;
4
-
5
- const DIRTY = 2;
6
-
7
- const DISPOSED = 3;
8
-
9
-
10
- const COMPUTED = 0;
11
-
12
- const EFFECT = 1;
13
-
14
- const SIGNAL = 2;
15
-
16
-
17
- const DISPOSE = Symbol();
18
-
19
- const RESET = Symbol();
20
-
21
- const UPDATE = Symbol();
22
-
23
-
24
- const NODE = Symbol();
25
-
26
- const NODES = Symbol();
27
-
28
-
29
- export { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, DISPOSE, EFFECT, NODE, NODES, RESET, SIGNAL, UPDATE };