@esportsplus/reactivity 0.0.22 → 0.0.24

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 (52) hide show
  1. package/build/api/effect.d.ts +2 -0
  2. package/build/api/effect.js +3 -0
  3. package/build/api/index.d.ts +4 -0
  4. package/build/api/index.js +4 -0
  5. package/build/api/macro.d.ts +4 -0
  6. package/build/api/macro.js +8 -0
  7. package/build/api/promise.d.ts +3 -0
  8. package/build/api/promise.js +38 -0
  9. package/build/api/reactive.d.ts +16 -0
  10. package/build/api/reactive.js +27 -0
  11. package/build/context/index.d.ts +5 -0
  12. package/build/context/index.js +3 -0
  13. package/build/context/node.d.ts +7 -0
  14. package/build/context/node.js +21 -0
  15. package/build/context/nodes.d.ts +7 -0
  16. package/build/context/nodes.js +33 -0
  17. package/build/index.d.ts +3 -12
  18. package/build/index.js +3 -4
  19. package/build/signal.d.ts +31 -0
  20. package/build/signal.js +256 -0
  21. package/build/symbols.d.ts +10 -1
  22. package/build/symbols.js +10 -1
  23. package/build/types.d.ts +33 -13
  24. package/build/types.js +2 -1
  25. package/package.json +2 -2
  26. package/readme.md +2 -0
  27. package/src/api/effect.ts +5 -0
  28. package/src/api/index.ts +4 -0
  29. package/src/api/macro.ts +15 -0
  30. package/src/api/promise.ts +45 -0
  31. package/src/api/reactive.ts +42 -0
  32. package/src/context/index.ts +5 -0
  33. package/src/context/node.ts +36 -0
  34. package/src/context/nodes.ts +52 -0
  35. package/src/index.ts +3 -6
  36. package/src/signal.ts +344 -0
  37. package/src/symbols.ts +22 -1
  38. package/src/types.ts +40 -13
  39. package/tsconfig.json +1 -1
  40. package/build/index.browser.js +0 -277
  41. package/build/methods/effect.d.ts +0 -2
  42. package/build/methods/effect.js +0 -4
  43. package/build/methods/index.d.ts +0 -3
  44. package/build/methods/index.js +0 -3
  45. package/build/methods/reactive.d.ts +0 -3
  46. package/build/methods/reactive.js +0 -52
  47. package/build/reactive.d.ts +0 -22
  48. package/build/reactive.js +0 -190
  49. package/src/methods/effect.ts +0 -6
  50. package/src/methods/index.ts +0 -5
  51. package/src/methods/reactive.ts +0 -71
  52. package/src/reactive.ts +0 -266
@@ -0,0 +1,45 @@
1
+ import { computed, read, root, signal, write } from '~/signal';
2
+ import context from '~/context';
3
+
4
+
5
+ export default (fn: <A, R extends Promise<any>>(...args: A[]) => R, options: Parameters<typeof computed>[1] = {}) => {
6
+ let input: unknown,
7
+ nodes = {
8
+ data: signal(options?.value, options),
9
+ status: signal(undefined, options)
10
+ };
11
+
12
+ function host(this: typeof host, ...args: Parameters<typeof fn>) {
13
+ input = args;
14
+
15
+ root(() => {
16
+ fn(...args)
17
+ .then(<T>(value: T) => {
18
+ write(nodes.data, value);
19
+ })
20
+ .catch(() => {
21
+ write(nodes.data, undefined);
22
+ });
23
+ });
24
+ }
25
+
26
+ Object.defineProperties(host, {
27
+ data: {
28
+ get() {
29
+ return read(nodes.data);
30
+ }
31
+ },
32
+ input: {
33
+ get() {
34
+ return input;
35
+ }
36
+ },
37
+ status: {
38
+ get() {
39
+ return read(nodes.status);
40
+ }
41
+ }
42
+ });
43
+
44
+ return context.nodes(host, nodes);
45
+ };
@@ -0,0 +1,42 @@
1
+ import { computed, read, signal, write } from '~/signal';
2
+ import { Computed, Context, Infer, Signal } from '~/types';
3
+ import context from '~/context';
4
+
5
+
6
+ type Data = {
7
+ [key in keyof Context]: never
8
+ } & Record<PropertyKey, Parameters<typeof computed>[0] | Parameters<typeof signal>[0]>;
9
+
10
+ type Options = Parameters<typeof computed>[1] | Parameters<typeof signal>[1];
11
+
12
+
13
+ export default <T>(data: Data, options: Options = {}) => {
14
+ let host = {},
15
+ nodes: Record<PropertyKey, Signal<any>> = {};
16
+
17
+ for (let key in data) {
18
+ if (typeof data[key] === 'function') {
19
+ nodes[key] = computed(data[key] as Computed<T>['fn'], options);
20
+
21
+ Object.defineProperty(host, key, {
22
+ get() {
23
+ return read(nodes[key]);
24
+ }
25
+ });
26
+ }
27
+ else {
28
+ nodes[key] = signal(data[key], options);
29
+
30
+ Object.defineProperty(host, key, {
31
+ get() {
32
+ return read(nodes[key]);
33
+ },
34
+ set(data) {
35
+ write(nodes[key], data);
36
+ }
37
+ });
38
+ }
39
+ }
40
+
41
+ return context.nodes(host as Infer<typeof data>, nodes);
42
+ };
@@ -0,0 +1,5 @@
1
+ import node from './node';
2
+ import nodes from './nodes';
3
+
4
+
5
+ export default { node, nodes };
@@ -0,0 +1,36 @@
1
+ import { NODE } from '~/symbols';
2
+ import { Context, Event, Listener, 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 Required<typeof host>;
36
+ };
@@ -0,0 +1,52 @@
1
+ import { NODES } from '~/symbols';
2
+ import { Context, Event, Listener, 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 Required<typeof host>;
52
+ };
package/src/index.ts CHANGED
@@ -1,6 +1,3 @@
1
- import { scheduler } from './reactive';
2
- import { effect, reactive } from './methods';
3
-
4
-
5
- export default { effect, reactive, scheduler };
6
- export { effect, reactive, scheduler };
1
+ export * from './api';
2
+ export * as core from './signal';
3
+ export { DISPOSE, RESET, UPDATE } from './symbols';
package/src/signal.ts ADDED
@@ -0,0 +1,344 @@
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';
3
+
4
+
5
+ let index = 0,
6
+ observer: Signal<any> | null = null,
7
+ observers: Signal<any>[] | null = null,
8
+ scope: Root | null = null;
9
+
10
+
11
+ class Signal<T> {
12
+ changed: Changed | null = null;
13
+ fn: Computed<T>['fn'] | null = null;
14
+ listeners: Record<symbol, (Listener | null)[]> | null = null;
15
+ observers: Signal<T>[] | null = null;
16
+ root: Root | null = null;
17
+ sources: Signal<T>[] | null = null;
18
+ state: State;
19
+ task: Parameters<Scheduler>[0] | null = null;
20
+ type: Type;
21
+ updating: boolean | null = null;
22
+ value: Computed<T>['value'] | T;
23
+
24
+
25
+ constructor(data: T, state: Signal<T>['state'], type: Signal<T>['type'], options: Options = {}) {
26
+ if (options?.changed) {
27
+ this.changed = options.changed;
28
+ }
29
+
30
+ this.state = state;
31
+ this.type = type;
32
+ this.value = data;
33
+ }
34
+
35
+
36
+ dispose() {
37
+ if (this.state === DISPOSED) {
38
+ return;
39
+ }
40
+
41
+ this.state = DISPOSED;
42
+
43
+ dispatch(DISPOSE, this);
44
+ flush(this);
45
+ }
46
+
47
+ on(event: Event, listener: Listener) {
48
+ if (this.updating) {
49
+ listener.once = true;
50
+ }
51
+
52
+ if (!this.listeners?.[event]) {
53
+ this.listeners ??= {};
54
+ this.listeners[event] = [listener];
55
+ }
56
+ else {
57
+ let listeners = this.listeners[event];
58
+
59
+ if (listeners.indexOf(listener) === -1) {
60
+ let i = listeners.indexOf(null);
61
+
62
+ if (i === -1) {
63
+ listeners.push(listener);
64
+ }
65
+ else {
66
+ listeners[i] = listener;
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ once(event: Event, listener: Listener) {
73
+ listener.once = true;
74
+ this.on(event, listener);
75
+ }
76
+
77
+ reset() {
78
+ dispatch(RESET, this);
79
+ flush(this);
80
+
81
+ if (this.type === COMPUTED) {
82
+ this.state = DIRTY;
83
+ this.value = undefined as T;
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
+ }
92
+ }
93
+ }
94
+
95
+
96
+ function changed(a: unknown, b: unknown) {
97
+ return a !== b;
98
+ }
99
+
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
+ function flush<T>(node: Signal<T>) {
124
+ if (node.sources) {
125
+ removeSourceObservers(node, 0);
126
+ }
127
+
128
+ node.listeners = null;
129
+ node.observers = null;
130
+ node.sources = null;
131
+ }
132
+
133
+ function notify<T>(nodes: Signal<T>[] | null, state: typeof CHECK | typeof DIRTY) {
134
+ if (!nodes) {
135
+ return;
136
+ }
137
+
138
+ for (let i = 0, n = nodes.length; i < n; i++) {
139
+ let node = nodes[i];
140
+
141
+ if (node.state < state) {
142
+ if (node.type === EFFECT && node.state === CLEAN) {
143
+ node.root!.scheduler(node.task!);
144
+ }
145
+
146
+ node.state = state;
147
+ notify(node.observers, CHECK);
148
+ }
149
+ }
150
+ }
151
+
152
+ function removeSourceObservers<T>(node: Signal<T>, start: number) {
153
+ if (!node.sources) {
154
+ return;
155
+ }
156
+
157
+ for (let i = start, n = node.sources.length; i < n; i++) {
158
+ let source = node.sources[i];
159
+
160
+ if (!source?.observers) {
161
+ continue;
162
+ }
163
+
164
+ source.observers[source.observers.indexOf(node)] = source.observers[source.observers.length - 1];
165
+ source.observers.pop();
166
+ }
167
+ }
168
+
169
+ function sync<T>(node: Signal<T>) {
170
+ if (node.state === CHECK && node.sources) {
171
+ for (let i = 0, n = node.sources.length; i < n; i++) {
172
+ sync(node.sources[i]);
173
+
174
+ // Stop the loop here so we won't trigger updates on other parents unnecessarily
175
+ // If our computation changes to no longer use some sources, we don't
176
+ // want to update() a source we used last time, but now don't use.
177
+ if ((node.state as State) === DIRTY) {
178
+ break;
179
+ }
180
+ }
181
+ }
182
+
183
+ if (node.state === DIRTY) {
184
+ update(node);
185
+ }
186
+ else {
187
+ node.state = CLEAN;
188
+ }
189
+ }
190
+
191
+ function update<T>(node: Signal<T>) {
192
+ let i = index,
193
+ o = observer,
194
+ os = observers;
195
+
196
+ index = 0;
197
+ observer = node;
198
+ observers = null as typeof observers;
199
+
200
+ try {
201
+ dispatch(UPDATE, node);
202
+
203
+ node.updating = true;
204
+
205
+ let value = node.fn!.call(node as Context, node?.value);
206
+
207
+ node.updating = null;
208
+
209
+ if (observers) {
210
+ removeSourceObservers(node, index);
211
+
212
+ if (node.sources && index > 0) {
213
+ node.sources.length = index + observers.length;
214
+
215
+ for (let i = 0, n = observers.length; i < n; i++) {
216
+ node.sources[index + i] = observers[i];
217
+ }
218
+ }
219
+ else {
220
+ node.sources = observers;
221
+ }
222
+
223
+ for (let i = index, n = node.sources.length; i < n; i++) {
224
+ let source = node.sources[i];
225
+
226
+ if (!source.observers) {
227
+ source.observers = [node];
228
+ }
229
+ else {
230
+ source.observers.push(node);
231
+ }
232
+ }
233
+ }
234
+ else if (node.sources && index < node.sources.length) {
235
+ removeSourceObservers(node, index);
236
+ node.sources.length = index;
237
+ }
238
+
239
+ if (node.type === COMPUTED) {
240
+ write(node, value);
241
+ }
242
+ }
243
+ catch {
244
+ if (node.state === DIRTY) {
245
+ removeSourceObservers(node, 0);
246
+ }
247
+ }
248
+ finally {
249
+ index = i;
250
+ observer = o;
251
+ observers = os;
252
+ }
253
+
254
+ node.state = CLEAN;
255
+ }
256
+
257
+
258
+ const computed = <T>(fn: Computed<T>['fn'], options: Options & { value?: unknown } = {}) => {
259
+ let node = new Signal(options?.value as any, DIRTY, COMPUTED, options);
260
+
261
+ node.fn = fn;
262
+
263
+ return node as Computed<T>;
264
+ };
265
+
266
+ const effect = <T>(fn: Effect<T>['fn'], options: Options = {}) => {
267
+ if (!scope) {
268
+ throw new Error('Reactivity: `effects` cannot be created without a reactive root');
269
+ }
270
+
271
+ let node = new Signal(undefined as any, DIRTY, EFFECT, options);
272
+
273
+ node.fn = fn;
274
+ node.root = scope;
275
+ node.task = () => read(node);
276
+
277
+ update(node);
278
+
279
+ return node as Effect<void>;
280
+ };
281
+
282
+ const read = <T>(node: Signal<T>): typeof node['value'] => {
283
+ if (node.state === DISPOSED) {
284
+ return node.value;
285
+ }
286
+
287
+ if (observer) {
288
+ if (!observers) {
289
+ if (observer?.sources?.[index] == node) {
290
+ index++;
291
+ }
292
+ else {
293
+ observers = [node];
294
+ }
295
+ }
296
+ else {
297
+ observers.push(node);
298
+ }
299
+ }
300
+
301
+ if (node.fn) {
302
+ sync(node);
303
+ }
304
+
305
+ return node.value;
306
+ };
307
+
308
+ const root = <T>(fn: () => T, properties: { scheduler?: Scheduler } = {}) => {
309
+ let o = observer,
310
+ s = scope;
311
+
312
+ properties.scheduler = properties?.scheduler || scope?.scheduler;
313
+
314
+ if (!properties.scheduler) {
315
+ throw new Error('Reactivity: `root` cannot be created without a task scheduler');
316
+ }
317
+
318
+ observer = null;
319
+ scope = properties as Root;
320
+
321
+ let result = fn();
322
+
323
+ observer = o;
324
+ scope = s;
325
+
326
+ return result;
327
+ };
328
+
329
+ const signal = <T>(data: T, options: Options = {}) => {
330
+ return new Signal(data, CLEAN, SIGNAL, options);
331
+ };
332
+
333
+ const write = <T>(node: Signal<T>, value: unknown) => {
334
+ if ((node?.changed || changed)(node.value, value)) {
335
+ node.value = value as T;
336
+ notify(node.observers, DIRTY);
337
+ }
338
+
339
+ return node.value;
340
+ };
341
+
342
+
343
+ export default Signal;
344
+ export { computed, effect, read, root, signal, write };
package/src/symbols.ts CHANGED
@@ -4,5 +4,26 @@ const CHECK = 1;
4
4
 
5
5
  const DIRTY = 2;
6
6
 
7
+ const DISPOSED = 3;
7
8
 
8
- export { CLEAN, CHECK, DIRTY };
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 };
package/src/types.ts CHANGED
@@ -1,28 +1,55 @@
1
- import { CLEAN, CHECK, DIRTY } from './symbols';
1
+ import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, SIGNAL } from './symbols';
2
+ import Signal from './signal';
2
3
 
3
4
 
4
- type Fn = () => Promise<unknown> | unknown;
5
+ type Changed = (a: unknown, b: unknown) => boolean;
6
+
7
+ type Computed<T> = {
8
+ fn: T extends Promise<unknown> ? never : ((this: Context, previous: T) => T);
9
+ value: ReturnType<Computed<T>['fn']>;
10
+ } & Omit<Signal<T>, 'fn' | 'value'>;
11
+
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
+ type Effect<T> = {
20
+ fn: (this: Context, previous: T) => T;
21
+ root: NonNullable<Signal<T>['root']>;
22
+ task: NonNullable<Signal<T>['task']>
23
+ } & Omit<Computed<T>, 'fn' | 'root' | 'task'>;
24
+
25
+ type Event = symbol;
5
26
 
6
27
  type Infer<T> =
7
28
  T extends (...args: any[]) => any
8
29
  ? ReturnType<T>
9
- : T extends Record<string, Primitives>
30
+ : T extends Record<PropertyKey, unknown>
10
31
  ? { [K in keyof T]: Infer<T[K]> }
11
32
  : T;
12
33
 
13
- type Primitives = any[] | boolean | number | string | null | undefined | ((...args: any[]) => any);
34
+ type Listener = {
35
+ once?: boolean;
14
36
 
15
- type ReactiveFn<T> = (onCleanup?: (fn: VoidFunction) => void) => T;
37
+ <T>(value: T): void;
38
+ };
39
+
40
+ type Options = {
41
+ changed?: Changed;
42
+ };
16
43
 
17
- type Scheduler = {
18
- schedule(): void;
19
- tasks: {
20
- add: (fn: Fn) => void;
21
- delete: (fn: Fn) => void;
22
- }
44
+ type Root = {
45
+ scheduler: Scheduler
23
46
  };
24
47
 
25
- type State = typeof CHECK | typeof CLEAN | typeof DIRTY;
48
+ type Scheduler = (fn: (...args: unknown[]) => Promise<unknown> | unknown) => unknown;
49
+
50
+ type State = typeof CHECK | typeof CLEAN | typeof DIRTY | typeof DISPOSED;
51
+
52
+ type Type = typeof COMPUTED | typeof EFFECT | typeof SIGNAL;
26
53
 
27
54
 
28
- export { Infer, ReactiveFn, Scheduler, State };
55
+ export { Changed, Computed, Context, Effect, Event, Infer, Listener, Options, Root, Scheduler, Signal, State, Type };
package/tsconfig.json CHANGED
@@ -5,6 +5,6 @@
5
5
  "outDir": "build",
6
6
  },
7
7
  "exclude": ["node_modules"],
8
- "extends": "@esportsplus/webpack/tsconfig.base.json",
8
+ "extends": "@esportsplus/rspack/tsconfig.base.json",
9
9
  "include": ["src"]
10
10
  }