@oscarpalmer/mora 0.29.0 → 0.30.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 (48) hide show
  1. package/dist/batch.mjs +15 -6
  2. package/dist/constants.d.mts +0 -1
  3. package/dist/constants.mjs +4 -3
  4. package/dist/effect.d.mts +5 -18
  5. package/dist/effect.mjs +23 -19
  6. package/dist/helpers/is.d.mts +9 -9
  7. package/dist/helpers/is.mjs +6 -0
  8. package/dist/helpers/proxy.d.mts +5 -9
  9. package/dist/helpers/proxy.mjs +4 -4
  10. package/dist/helpers/value.d.mts +3 -3
  11. package/dist/helpers/value.mjs +22 -5
  12. package/dist/index.d.mts +7 -8
  13. package/dist/index.mjs +1 -1
  14. package/dist/models.d.mts +356 -18
  15. package/dist/mora.full.mjs +448 -421
  16. package/dist/subscription.d.mts +1 -9
  17. package/dist/subscription.mjs +14 -15
  18. package/dist/value/array.d.mts +5 -133
  19. package/dist/value/array.mjs +91 -137
  20. package/dist/value/computed.d.mts +4 -12
  21. package/dist/value/computed.mjs +54 -50
  22. package/dist/value/reactive.d.mts +3 -49
  23. package/dist/value/reactive.mjs +10 -54
  24. package/dist/value/signal.d.mts +5 -21
  25. package/dist/value/signal.mjs +27 -49
  26. package/dist/value/store.d.mts +9 -92
  27. package/dist/value/store.mjs +42 -49
  28. package/package.json +7 -8
  29. package/src/batch.ts +22 -9
  30. package/src/constants.ts +3 -4
  31. package/src/effect.ts +35 -40
  32. package/src/helpers/is.ts +11 -10
  33. package/src/helpers/proxy.ts +23 -18
  34. package/src/helpers/value.ts +39 -5
  35. package/src/index.ts +6 -7
  36. package/src/models.ts +431 -16
  37. package/src/subscription.ts +20 -20
  38. package/src/value/array.ts +183 -265
  39. package/src/value/computed.ts +80 -74
  40. package/src/value/reactive.ts +16 -77
  41. package/src/value/signal.ts +34 -71
  42. package/src/value/store.ts +84 -177
  43. package/types/constants.d.ts +1 -1
  44. package/types/index.d.ts +1 -1
  45. package/types/value/array.d.ts +1 -1
  46. package/types/value/computed.d.ts +3 -0
  47. package/types/value/signal.d.ts +1 -1
  48. package/types/value/store.d.ts +1 -1
@@ -1,58 +1,100 @@
1
1
  import {flushHandlers} from '../batch';
2
- import {ACTIVE, BATCH, NAME_COMPUTED} from '../constants';
3
- import {effect, runEffect} from '../effect';
4
- import type {ComputedEffect, InternalComputed, ReactiveOptions, ReactiveState} from '../models';
5
- import {Reactive} from './reactive';
2
+ import {ACTIVE, BATCH, NAME_COMPUTED, NAME_MORA} from '../constants';
3
+ import {internalEffect, runEffect} from '../effect';
4
+ import {handleSimpleValue} from '../helpers/value';
5
+ import type {Computed, ComputedEffect, ReactiveOptions, ReactiveState} from '../models';
6
+ import {subscribe} from '../subscription';
7
+ import {reactive} from './reactive';
6
8
 
7
- export class Computed<Value> extends Reactive<Value> {
8
- private readonly effect: ComputedEffect = {
9
+ /**
10
+ * Create a computed value
11
+ *
12
+ * @param callback Callback to compute the value
13
+ * @param options Reactivity options
14
+ * @returns Computed value
15
+ */
16
+ export function computed<Value>(
17
+ callback: () => Value | Promise<Value>,
18
+ options?: ReactiveOptions<Value>,
19
+ ): Computed<Value> {
20
+ return getComputed(callback, options)[0];
21
+ }
22
+
23
+ function getComputed<Value>(
24
+ callback: () => Value | Promise<Value>,
25
+ options?: ReactiveOptions<Value>,
26
+ ): [Computed<Value>, ComputedEffect] {
27
+ const [rx, state] = reactive<Value>(undefined as never, options);
28
+
29
+ let fx: ComputedEffect;
30
+
31
+ const instance = {
32
+ ...rx,
33
+ get: () => getValue(state, fx),
34
+ peek: () => state.value,
35
+ subscribe: (callback: (value: Value) => void) => subscribe(state, callback),
36
+ unsubscribe: (callback: (value: Value) => void) => {
37
+ state.subscriptions.delete(callback);
38
+ },
39
+ };
40
+
41
+ Object.defineProperty(instance, NAME_MORA, {
42
+ enumerable: false,
43
+ value: NAME_COMPUTED,
44
+ });
45
+
46
+ fx = getComputedEffect(state, callback);
47
+
48
+ return [Object.freeze(instance), fx];
49
+ }
50
+
51
+ function getComputedEffect<Value>(
52
+ state: ReactiveState<Value, Value>,
53
+ callback: () => Value | Promise<Value>,
54
+ ): ComputedEffect {
55
+ const fx: ComputedEffect = {
9
56
  dirty: true,
10
57
  instance: undefined as never,
11
58
  };
12
59
 
13
- constructor(callback: () => Value | Promise<Value>, options?: ReactiveOptions<Value>) {
14
- super(NAME_COMPUTED, undefined as never, options);
60
+ fx.instance = internalEffect(() => {
61
+ if (fx.dirty) {
62
+ const previousComputed = ACTIVE.computed;
15
63
 
16
- this.effect.instance = effect(() => {
17
- if (this.effect.dirty) {
18
- setValue(this, this.state, callback);
64
+ ACTIVE.computed = fx;
19
65
 
20
- this.effect.dirty = false;
21
- }
22
- });
23
- }
66
+ handleSimpleValue(state, callback, setAndEmit, () => {
67
+ ACTIVE.computed = previousComputed;
68
+ });
24
69
 
25
- /**
26
- * @inheritdoc
27
- */
28
- get(): Value {
29
- if (ACTIVE.computed != null && this !== ACTIVE.computed) {
30
- this.state.computeds.add(ACTIVE.computed);
70
+ fx.dirty = false;
31
71
  }
72
+ });
32
73
 
33
- if (ACTIVE.effect != null && ACTIVE.effect !== this.effect.instance) {
34
- this.state.effects.add(ACTIVE.effect);
35
- }
74
+ return fx;
75
+ }
36
76
 
37
- if (this.effect.dirty && BATCH.depth === 0) {
38
- runEffect(this.effect.instance);
39
- }
77
+ function getValue<Value>(state: ReactiveState<Value, Value>, fx: ComputedEffect): Value {
78
+ if (ACTIVE.computed != null && fx !== ACTIVE.computed) {
79
+ state.computeds.add(ACTIVE.computed);
80
+ }
40
81
 
41
- return this.state.value;
82
+ if (ACTIVE.effect != null && ACTIVE.effect !== fx.instance) {
83
+ state.effects.add(ACTIVE.effect);
42
84
  }
85
+
86
+ if (fx.dirty && BATCH.depth === 0) {
87
+ runEffect(fx.instance);
88
+ }
89
+
90
+ return state.value;
43
91
  }
44
92
 
45
- /**
46
- * Create a computed value
47
- * @param callback Callback to compute the value
48
- * @param options Reactivity options
49
- * @returns Computed value
50
- */
51
- export function computed<Value>(
93
+ export function internalComputed<Value>(
52
94
  callback: () => Value | Promise<Value>,
53
95
  options?: ReactiveOptions<Value>,
54
- ): Computed<Value> {
55
- return new Computed(callback, options);
96
+ ): [Computed<Value>, ComputedEffect] {
97
+ return getComputed(callback, options);
56
98
  }
57
99
 
58
100
  function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): void {
@@ -63,7 +105,7 @@ function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): vo
63
105
  state.value = value;
64
106
 
65
107
  for (const computed of state.computeds) {
66
- (computed as unknown as InternalComputed).effect.dirty = true;
108
+ computed.dirty = true;
67
109
  }
68
110
 
69
111
  for (const effect of state.effects) {
@@ -76,39 +118,3 @@ function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): vo
76
118
 
77
119
  flushHandlers();
78
120
  }
79
-
80
- function setValue<Value>(
81
- instance: Computed<Value>,
82
- state: ReactiveState<Value, Value>,
83
- callback: () => Value | Promise<Value>,
84
- ): void {
85
- const previousComputed = ACTIVE.computed;
86
-
87
- ACTIVE.computed = instance as never;
88
-
89
- try {
90
- const value = callback();
91
-
92
- if (value instanceof Promise) {
93
- state.promise = value;
94
-
95
- value
96
- .then(resolvedValue => {
97
- if (state.promise === value) {
98
- state.promise = undefined;
99
-
100
- setAndEmit(state, resolvedValue);
101
- }
102
- })
103
- .catch(() => {
104
- if (state.promise === value) {
105
- state.promise = undefined;
106
- }
107
- });
108
- } else {
109
- setAndEmit(state, value);
110
- }
111
- } finally {
112
- ACTIVE.computed = previousComputed;
113
- }
114
- }
@@ -1,85 +1,24 @@
1
- import {NAME_MORA} from '../constants';
2
- import type {ReactiveOptions, ReactiveState, Unsubscribe} from '../models';
3
- import {subscribe, unsubscribe} from '../subscription';
1
+ import type {Reactive, ReactiveOptions, ReactiveState} from '../models';
4
2
 
5
- export abstract class Reactive<Value, Equal = Value> {
6
- /**
7
- * Internal name of the reactive value
8
- *
9
- * @internal
10
- */
11
- declare protected readonly $mora: string;
12
-
13
- /**
14
- * Internal state of the reactive value
15
- *
16
- * @internal
17
- */
18
- protected readonly state: ReactiveState<Value, Equal> = {
3
+ export function reactive<Value, Equal = Value>(
4
+ value: Value,
5
+ options?: ReactiveOptions<Equal>,
6
+ ): [Reactive<Value>, ReactiveState<Value, Equal>] {
7
+ const state: ReactiveState<Value, Equal> = {
19
8
  computeds: new Set(),
20
9
  effects: new Set(),
21
- equal: Object.is,
10
+ equal:
11
+ typeof options === 'object' && typeof options?.equal === 'function'
12
+ ? options.equal
13
+ : Object.is,
22
14
  subscriptions: new Map(),
23
- value: undefined as never,
15
+ value: value as Value,
24
16
  };
25
17
 
26
- constructor(name: string, value: Value, options?: ReactiveOptions<Equal>) {
27
- this.state.value = value;
28
-
29
- if (typeof options === 'object' && typeof options.equal === 'function') {
30
- this.state.equal = options.equal;
31
- }
32
-
33
- Object.defineProperty(this, NAME_MORA, {
34
- value: name,
35
- });
36
- }
37
-
38
- /**
39
- * Get the value
40
- * @return Current value
41
- */
42
- abstract get(): Value;
43
-
44
- /**
45
- * Get the value _(without reactivity)_
46
- * @return Current value
47
- */
48
- peek(): Value {
49
- return this.state.value;
50
- }
51
-
52
- /**
53
- * Subscribe to changes
54
- * @param callback Callback for changes
55
- * @return Unsubscribe callback
56
- */
57
- subscribe(callback: (value: Value) => void): Unsubscribe {
58
- return subscribe(this.state, callback);
59
- }
60
-
61
- /**
62
- * JSON representation of the value
63
- * @return JSON value
64
- */
65
- toJSON(): Value {
66
- return this.get();
67
- }
68
-
69
- /**
70
- * String representation of the value
71
- * @return Value as string
72
- */
73
- toString(): string {
74
- // oxlint-disable-next-line @oscarpalmer/atoms/string.getString: base toString is expected; allow custom toString in options?
75
- return String(this.get());
76
- }
18
+ const instance = {
19
+ toJSON: () => state.value,
20
+ toString: () => String(state.value),
21
+ };
77
22
 
78
- /**
79
- * Unsubscribe from changes
80
- * @param callback Callback to unsubscribe
81
- */
82
- unsubscribe(callback: (value: Value) => void): void {
83
- unsubscribe(this.state, callback);
84
- }
23
+ return [instance, state];
85
24
  }
@@ -1,36 +1,8 @@
1
- import {NAME_SIGNAL} from '../constants';
2
- import {emitValue, getValue} from '../helpers/value';
3
- import type {ReactiveOptions, ReactiveState} from '../models';
4
- import {Reactive} from './reactive';
5
-
6
- export class Signal<Value> extends Reactive<Value> {
7
- constructor(value: Value, options?: ReactiveOptions<Value>) {
8
- super(NAME_SIGNAL, value, options);
9
- }
10
-
11
- /**
12
- * @inheritdoc
13
- */
14
- get(): Value {
15
- return getValue(this.state);
16
- }
17
-
18
- /**
19
- * Set the value
20
- * @param value New value
21
- */
22
- set(value: Value | (() => Value | Promise<Value>) | Promise<Value>): void {
23
- setValue<Value>(this.state, value);
24
- }
25
-
26
- /**
27
- * Update the value _(based on the current value)_
28
- * @param callback Callback to update the value
29
- */
30
- update(callback: (value: Value) => Value): void {
31
- this.set(callback(this.state.value));
32
- }
33
- }
1
+ import {NAME_MORA, NAME_SIGNAL} from '../constants';
2
+ import {emitValue, getSimpleValue, handleSimpleValue} from '../helpers/value';
3
+ import type {ReactiveOptions, ReactiveState, Signal} from '../models';
4
+ import {subscribe} from '../subscription';
5
+ import {reactive} from './reactive';
34
6
 
35
7
  function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): void {
36
8
  if (!state.equal(state.value, value)) {
@@ -40,41 +12,9 @@ function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): vo
40
12
  }
41
13
  }
42
14
 
43
- function setValue<Value>(
44
- state: ReactiveState<Value, Value>,
45
- value: Value | (() => Value | Promise<Value>) | Promise<Value>,
46
- ): void {
47
- try {
48
- let actual = value;
49
-
50
- if (typeof value === 'function') {
51
- actual = (value as () => Value | Promise<Value>)() as Value | Promise<Value>;
52
- }
53
-
54
- if (actual instanceof Promise) {
55
- state.promise = actual;
56
-
57
- void actual
58
- .then(value => {
59
- if (actual === state.promise) {
60
- state.promise = undefined;
61
-
62
- setAndEmit(state, value);
63
- }
64
- })
65
- .catch(() => {
66
- if (actual === state.promise) {
67
- state.promise = undefined;
68
- }
69
- });
70
- } else {
71
- setAndEmit(state, actual as Value);
72
- }
73
- } catch {}
74
- }
75
-
76
15
  /**
77
16
  * Create a reactive value from a function result
17
+ *
78
18
  * @param value Initial value
79
19
  * @param options Reactivity options
80
20
  * @returns Reactive value
@@ -86,6 +26,7 @@ export function signal<Value>(
86
26
 
87
27
  /**
88
28
  * Create a reactive value from a promise
29
+ *
89
30
  * @param value Initial value
90
31
  * @param options Reactivity options
91
32
  * @returns Reactive value
@@ -97,6 +38,7 @@ export function signal<Value>(
97
38
 
98
39
  /**
99
40
  * Create a reactive value
41
+ *
100
42
  * @param value Initial value
101
43
  * @param options Reactivity options
102
44
  * @returns Reactive value
@@ -107,9 +49,30 @@ export function signal<Value>(
107
49
  value: Value | (() => Value | Promise<Value>) | Promise<Value>,
108
50
  options?: ReactiveOptions<Value>,
109
51
  ): Signal<Value> {
110
- const instance = new Signal<Value>(undefined as unknown as Value, options);
111
-
112
- instance.set(value);
113
-
114
- return instance;
52
+ const [rx, state] = reactive<Value>(undefined as unknown as Value, options);
53
+
54
+ const instance = {
55
+ ...rx,
56
+ get: () => getSimpleValue(state),
57
+ peek: () => state.value,
58
+ set: (value: Value | (() => Value | Promise<Value>) | Promise<Value>) => {
59
+ handleSimpleValue(state, value, setAndEmit);
60
+ },
61
+ update: (callback: (value: Value) => Value) => {
62
+ handleSimpleValue(state, callback(state.value), setAndEmit);
63
+ },
64
+ subscribe: (callback: (value: Value) => void) => subscribe(state, callback),
65
+ unsubscribe: (callback: (value: Value) => void) => {
66
+ state.subscriptions.delete(callback);
67
+ },
68
+ };
69
+
70
+ Object.defineProperty(instance, NAME_MORA, {
71
+ enumerable: false,
72
+ value: NAME_SIGNAL,
73
+ });
74
+
75
+ handleSimpleValue(state, value, setAndEmit);
76
+
77
+ return Object.freeze(instance) as Signal<Value>;
115
78
  }