@oscarpalmer/mora 0.32.0 → 0.33.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 (45) hide show
  1. package/dist/batch.mjs +2 -2
  2. package/dist/constants.d.mts +2 -0
  3. package/dist/constants.mjs +3 -1
  4. package/dist/effect.d.mts +1 -0
  5. package/dist/effect.mjs +8 -5
  6. package/dist/helpers/is.d.mts +6 -6
  7. package/dist/helpers/misc.d.mts +4 -0
  8. package/dist/helpers/misc.mjs +9 -0
  9. package/dist/helpers/proxy.d.mts +8 -10
  10. package/dist/helpers/proxy.mjs +71 -33
  11. package/dist/helpers/subscription.d.mts +4 -4
  12. package/dist/helpers/subscription.mjs +16 -13
  13. package/dist/helpers/value.d.mts +8 -7
  14. package/dist/helpers/value.mjs +35 -22
  15. package/dist/models.d.mts +47 -24
  16. package/dist/models.mjs +1 -0
  17. package/dist/mora.full.mjs +307 -236
  18. package/dist/value/array.d.mts +1 -0
  19. package/dist/value/array.mjs +69 -61
  20. package/dist/value/computed.d.mts +3 -1
  21. package/dist/value/computed.mjs +39 -25
  22. package/dist/value/readonly.d.mts +3 -3
  23. package/dist/value/readonly.mjs +31 -24
  24. package/dist/value/signal.d.mts +1 -0
  25. package/dist/value/signal.mjs +27 -20
  26. package/dist/value/store.d.mts +1 -0
  27. package/dist/value/store.mjs +23 -41
  28. package/package.json +1 -1
  29. package/src/batch.ts +2 -2
  30. package/src/constants.ts +4 -0
  31. package/src/effect.ts +16 -11
  32. package/src/helpers/is.ts +6 -6
  33. package/src/helpers/misc.ts +15 -0
  34. package/src/helpers/proxy.ts +116 -89
  35. package/src/helpers/subscription.ts +46 -35
  36. package/src/helpers/value.ts +61 -35
  37. package/src/models.ts +54 -28
  38. package/src/value/array.ts +103 -124
  39. package/src/value/computed.ts +70 -40
  40. package/src/value/readonly.ts +54 -41
  41. package/src/value/signal.ts +46 -35
  42. package/src/value/store.ts +36 -83
  43. package/dist/value/reactive.d.mts +0 -4
  44. package/dist/value/reactive.mjs +0 -15
  45. package/src/value/reactive.ts +0 -27
@@ -1,5 +1,6 @@
1
1
  import { ReactiveArray, ReactiveOptions } from "../models.mjs";
2
2
  //#region src/value/array.d.ts
3
+ declare function ReactiveArray(this: any, value: unknown, options?: ReactiveOptions<unknown>): void;
3
4
  /**
4
5
  * Create a reactive array from a function result
5
6
  *
@@ -1,7 +1,7 @@
1
- import { METHODS_AFFECTING_LENGTH, METHODS_UPDATE, NAME_ARRAY, NAME_MORA } from "../constants.mjs";
2
- import { emitValue, equalArrays } from "../helpers/value.mjs";
1
+ import { METHODS_AFFECTING_LENGTH, METHODS_UPDATE, NAME_ARRAY, NAME_MORA, PROPERTY_LENGTH, SYMBOL_STATE } from "../constants.mjs";
2
+ import { emitValue, equalArrays, getStringValue } from "../helpers/value.mjs";
3
+ import { getState } from "../helpers/misc.mjs";
3
4
  import { subscribeToProxy } from "../helpers/subscription.mjs";
4
- import { reactive } from "./reactive.mjs";
5
5
  import { computed } from "./computed.mjs";
6
6
  import { emitProxyValues, getValueInProxy, peekValueInProxy, setProxyValue, setValueInProxy, updateProxyValue } from "../helpers/proxy.mjs";
7
7
  import { getReadonlyInstance } from "./readonly.mjs";
@@ -9,77 +9,85 @@ import { signal } from "./signal.mjs";
9
9
  import { select } from "@oscarpalmer/atoms/array";
10
10
  import { filter } from "@oscarpalmer/atoms/array/filter";
11
11
  //#region src/value/array.ts
12
- function array(value, options) {
13
- const [rx, state] = reactive([], options);
14
- const isArray = true;
15
- const length = signal(0);
16
- const mapped = /* @__PURE__ */ new Map();
17
- const readonlies = {};
18
- state.value = new Proxy([], {
19
- get: (target, property) => METHODS_UPDATE.has(property) ? updateArray(property, target, state, length) : Reflect.get(target, property),
20
- set: (target, property, value) => setValueInProxy(isArray, state, target, property, value, length)
21
- });
22
- function get(value) {
23
- return value === "length" ? length.get() : getValueInProxy(isArray, instance, state, mapped, value);
24
- }
25
- function set(first, second) {
26
- setProxyValue(true, state, setArrayValue, setAtIndex, first, second);
27
- }
28
- const instance = {
29
- ...rx,
30
- asReadonly: (frozen) => getReadonlyInstance(state, readonlies, frozen === true),
31
- at: (index) => get(index),
32
- clear: () => {
33
- state.value.length = 0;
34
- },
35
- filter: (callback) => computed(() => filter(get(), callback)),
36
- get: (value) => get(value),
37
- map: (callback) => computed(() => get().map(callback)),
38
- notify: () => emitProxyValues(state, mapped),
39
- peek: (first, second) => peekArrayValue(state, length, first, second),
40
- pop: () => state.value.pop(),
41
- push: (...items) => state.value.push(...items),
42
- select: (filter, map) => computed(() => select(get(), filter, map)),
43
- set: (first, second) => set(first, second),
44
- shift: () => state.value.shift(),
45
- splice: (from, to, ...items) => state.value.splice(from, to ?? state.value.length, ...items),
46
- subscribe: (first, second, third) => subscribeToProxy(isArray, instance, state, mapped, first, second, third),
47
- unshift: (...items) => state.value.unshift(...items),
48
- update: (callback) => updateProxyValue(true, state, setArrayValue, setAtIndex, callback)
12
+ function ReactiveArray(value, options) {
13
+ this[SYMBOL_STATE] = {
14
+ ...getState(void 0, options),
15
+ isArray: true,
16
+ length: signal(0)
49
17
  };
50
- Object.defineProperties(instance, {
51
- [NAME_MORA]: { value: NAME_ARRAY },
52
- length: {
53
- enumerable: true,
54
- get: () => length.get(),
55
- set: (value) => setArrayLength(state, value)
56
- }
18
+ this[SYMBOL_STATE].value = new Proxy([], {
19
+ get: (target, property) => METHODS_UPDATE.has(property) ? updateArray(this, property, target) : Reflect.get(target, property),
20
+ set: (target, property, value) => setValueInProxy(this, target, property, value)
57
21
  });
58
- set(value);
59
- return Object.freeze(instance);
22
+ Object.defineProperty(this, PROPERTY_LENGTH, {
23
+ enumerable: true,
24
+ get: () => this[SYMBOL_STATE].length.get(),
25
+ set: (value) => setArrayLength(this[SYMBOL_STATE], value)
26
+ });
27
+ setProxyValue.call(this, value);
60
28
  }
61
- function peekArrayValue(state, length, first, second) {
62
- return first === "length" ? length.peek() : peekValueInProxy(true, state, first, second);
29
+ ReactiveArray.prototype[NAME_MORA] = NAME_ARRAY;
30
+ ReactiveArray.prototype.asReadonly = getReadonlyInstance;
31
+ ReactiveArray.prototype.at = getArrayValues;
32
+ ReactiveArray.prototype.get = getArrayValues;
33
+ ReactiveArray.prototype.notify = emitProxyValues;
34
+ ReactiveArray.prototype.peek = peekArrayValue;
35
+ ReactiveArray.prototype.set = setProxyValue;
36
+ ReactiveArray.prototype.subscribe = subscribeToProxy;
37
+ ReactiveArray.prototype.toString = getStringValue;
38
+ ReactiveArray.prototype.update = updateProxyValue;
39
+ ReactiveArray.prototype.clear = function() {
40
+ this[SYMBOL_STATE].value.length = 0;
41
+ };
42
+ ReactiveArray.prototype.filter = function(callback) {
43
+ return computed(() => filter(getArrayValues.call(this), callback));
44
+ };
45
+ ReactiveArray.prototype.map = function(callback) {
46
+ return computed(() => getArrayValues.call(this).map(callback));
47
+ };
48
+ ReactiveArray.prototype.pop = function() {
49
+ return this[SYMBOL_STATE].value.pop();
50
+ };
51
+ ReactiveArray.prototype.push = function(...items) {
52
+ return this[SYMBOL_STATE].value.push(...items);
53
+ };
54
+ ReactiveArray.prototype.select = function(filter, map) {
55
+ return computed(() => select(getArrayValues.call(this), filter, map));
56
+ };
57
+ ReactiveArray.prototype.shift = function() {
58
+ return this[SYMBOL_STATE].value.shift();
59
+ };
60
+ ReactiveArray.prototype.splice = function(from, to, ...items) {
61
+ return this[SYMBOL_STATE].value.splice(from, to ?? this[SYMBOL_STATE].value.length, ...items);
62
+ };
63
+ ReactiveArray.prototype.toJSON = function() {
64
+ return this[SYMBOL_STATE].value;
65
+ };
66
+ ReactiveArray.prototype.unshift = function(...items) {
67
+ return this[SYMBOL_STATE].value.unshift(...items);
68
+ };
69
+ function array(value, options) {
70
+ return new ReactiveArray(value, options);
63
71
  }
64
- function setArrayLength(state, value) {
65
- if (typeof value === "number" && !Number.isNaN(value) && value >= 0 && value !== state.value.length) state.value.length = value;
72
+ function getArrayValues(value) {
73
+ return value === "length" ? this[SYMBOL_STATE].length.get() : getValueInProxy.call(this, value);
66
74
  }
67
- function setArrayValue(state, value) {
68
- state.value.splice(0, state.value.length, ...value ?? []);
75
+ function peekArrayValue(first, second) {
76
+ return first === "length" ? this[SYMBOL_STATE].length.peek() : peekValueInProxy.call(this, first, second);
69
77
  }
70
- function setAtIndex(state, index, value) {
71
- const actual = index < 0 ? state.value.length + index : index;
72
- if (actual > -1) state.value[actual] = value;
78
+ function setArrayLength(state, value) {
79
+ if (typeof value === "number" && !Number.isNaN(value) && value >= 0 && value !== state.value.length) state.value.length = value;
73
80
  }
74
- function updateArray(type, array, state, length) {
81
+ function updateArray(instance, type, array) {
82
+ const state = instance[SYMBOL_STATE];
75
83
  const affectsLength = METHODS_AFFECTING_LENGTH.has(type);
76
84
  const previousArray = affectsLength ? [] : array.slice();
77
85
  const previousLength = array.length;
78
86
  return (...args) => {
79
87
  const result = array[type](...args);
80
88
  if (affectsLength ? array.length !== previousLength : !equalArrays(state, previousArray, array)) {
81
- emitValue(state);
82
- length.set(array.length);
89
+ emitValue(instance);
90
+ state.length.set(array.length);
83
91
  }
84
92
  return result;
85
93
  };
@@ -1,5 +1,7 @@
1
1
  import { Computed, ComputedEffect, ReactiveOptions } from "../models.mjs";
2
+ import { GenericCallback } from "@oscarpalmer/atoms/models";
2
3
  //#region src/value/computed.d.ts
4
+ declare function Computed(this: any, callback: GenericCallback, options?: ReactiveOptions<unknown>): void;
3
5
  /**
4
6
  * Create a computed value
5
7
  *
@@ -8,5 +10,5 @@ import { Computed, ComputedEffect, ReactiveOptions } from "../models.mjs";
8
10
  * @returns Computed value
9
11
  */
10
12
  export declare function computed<Value>(callback: () => Value | Promise<Value>, options?: ReactiveOptions<Value>): Computed<Value>;
11
- export declare function internalComputed<Value>(callback: () => Value | Promise<Value>, options?: ReactiveOptions<Value>): [Computed<Value>, ComputedEffect];
13
+ export declare function internalComputed(callback: GenericCallback, options?: ReactiveOptions<unknown>): [Computed<unknown>, ComputedEffect];
12
14
  //#endregion
@@ -1,10 +1,22 @@
1
- import { ACTIVE, BATCH, NAME_COMPUTED, NAME_MORA, SUBSCRIPTION_TYPES, SUBSCRIPTION_TYPES_COPY } from "../constants.mjs";
1
+ import { ACTIVE, BATCH, NAME_COMPUTED, NAME_MORA, SUBSCRIPTION_TYPES, SUBSCRIPTION_TYPES_COPY, SYMBOL_EFFECT, SYMBOL_STATE } from "../constants.mjs";
2
2
  import { internalEffect, runEffect } from "../effect.mjs";
3
- import { handleSimpleValue, peekSimpleValue } from "../helpers/value.mjs";
3
+ import { getStringValue, handleSignalValue, peekSignalValue } from "../helpers/value.mjs";
4
4
  import { flushHandlers } from "../batch.mjs";
5
+ import { getState } from "../helpers/misc.mjs";
5
6
  import { subscribeToSignal } from "../helpers/subscription.mjs";
6
- import { reactive } from "./reactive.mjs";
7
7
  //#region src/value/computed.ts
8
+ function Computed(callback, options) {
9
+ this[SYMBOL_STATE] = getState(void 0, options);
10
+ this[SYMBOL_EFFECT] = getComputedEffect(this, callback);
11
+ }
12
+ Computed.prototype[NAME_MORA] = NAME_COMPUTED;
13
+ Computed.prototype.get = getComputedValue;
14
+ Computed.prototype.peek = peekSignalValue;
15
+ Computed.prototype.subscribe = subscribeToSignal;
16
+ Computed.prototype.toString = getStringValue;
17
+ Computed.prototype.toJSON = function() {
18
+ return this[SYMBOL_STATE].value;
19
+ };
8
20
  /**
9
21
  * Create a computed value
10
22
  *
@@ -17,19 +29,10 @@ function computed(callback, options) {
17
29
  }
18
30
  function getComputed(callback, options) {
19
31
  if (typeof callback !== "function") throw new TypeError("Computed callback must be a function");
20
- const [rx, state] = reactive(void 0, options);
21
- let fx;
22
- const instance = {
23
- ...rx,
24
- get: () => getValue(state, fx),
25
- peek: (copy) => peekSimpleValue(state.value, copy === true),
26
- subscribe: (callback, copy) => subscribeToSignal(state, callback, copy === true)
27
- };
28
- Object.defineProperty(instance, NAME_MORA, { value: NAME_COMPUTED });
29
- fx = getComputedEffect(state, callback);
30
- return [Object.freeze(instance), fx];
32
+ const instance = new Computed(callback, options);
33
+ return [instance, instance[SYMBOL_EFFECT]];
31
34
  }
32
- function getComputedEffect(state, callback) {
35
+ function getComputedEffect(instance, callback) {
33
36
  const fx = {
34
37
  dirty: true,
35
38
  instance: void 0
@@ -38,7 +41,7 @@ function getComputedEffect(state, callback) {
38
41
  if (fx.dirty) {
39
42
  const previousComputed = ACTIVE.computed;
40
43
  ACTIVE.computed = fx;
41
- handleSimpleValue(state, callback, setAndEmit, () => {
44
+ handleSignalValue(instance, callback, setAndEmit, () => {
42
45
  ACTIVE.computed = previousComputed;
43
46
  });
44
47
  fx.dirty = false;
@@ -46,25 +49,36 @@ function getComputedEffect(state, callback) {
46
49
  });
47
50
  return fx;
48
51
  }
49
- function getValue(state, fx) {
50
- if (ACTIVE.computed != null && fx !== ACTIVE.computed) state.computeds.add(ACTIVE.computed);
51
- if (ACTIVE.effect != null && ACTIVE.effect !== fx.instance) state.effects.add(ACTIVE.effect);
52
+ function getComputedValue() {
53
+ const fx = this[SYMBOL_EFFECT];
54
+ const state = this[SYMBOL_STATE];
55
+ if (ACTIVE.computed != null && fx !== ACTIVE.computed) {
56
+ state.computeds ??= /* @__PURE__ */ new Set();
57
+ state.computeds.add(ACTIVE.computed);
58
+ }
59
+ if (ACTIVE.effect != null && ACTIVE.effect !== fx.instance) {
60
+ state.effects ??= /* @__PURE__ */ new Set();
61
+ state.effects.add(ACTIVE.effect);
62
+ }
52
63
  if (fx.dirty && BATCH.depth === 0) runEffect(fx.instance);
53
64
  return state.value;
54
65
  }
55
66
  function internalComputed(callback, options) {
56
67
  return getComputed(callback, options);
57
68
  }
58
- function setAndEmit(state, value) {
59
- if (state.equal(state.value, value)) return;
69
+ function setAndEmit(instance, value) {
70
+ const state = instance[SYMBOL_STATE];
71
+ if ((state.equal ?? Object.is)(state.value, value)) return;
60
72
  state.value = value;
61
- for (const computed of state.computeds) computed.dirty = true;
62
- for (const effect of state.effects) BATCH.handlers.set(effect, effect);
73
+ if (state.computeds != null && state.computeds.size > 0) for (const computed of state.computeds) computed.dirty = true;
74
+ if (state.effects != null && state.effects.size > 0) for (const effect of state.effects) BATCH.handlers.set(effect, effect);
63
75
  for (const type of SUBSCRIPTION_TYPES) {
64
76
  if (type === "frozen") continue;
65
- const copy = SUBSCRIPTION_TYPES_COPY.has(type);
66
77
  const subscriptions = state.subscriptions?.values.to.keyed?.get(type);
67
- if (subscriptions != null) for (const [, callback] of subscriptions) callback(peekSimpleValue(state.value, copy));
78
+ if (subscriptions != null && subscriptions.size > 0) {
79
+ const copy = SUBSCRIPTION_TYPES_COPY.has(type);
80
+ for (const [, callback] of subscriptions) callback(peekSignalValue.call(instance, copy));
81
+ }
68
82
  }
69
83
  flushHandlers();
70
84
  }
@@ -1,5 +1,5 @@
1
- import { ReactiveState, ReadonlyFrozenSignal, ReadonlyInstances, ReadonlySignal } from "../models.mjs";
1
+ import { InternalSignal, ReadonlyFrozenSignal, ReadonlySignal, SignalState } from "../models.mjs";
2
2
  //#region src/value/readonly.d.ts
3
- export declare function getReadonlyInstance<Value>(state: ReactiveState<Value, never>, instances: ReadonlyInstances<Value>, frozen: boolean): ReadonlySignal<Value>;
4
- export declare function getReadonlySignal<Value>(state: ReactiveState<Value, never>, frozen: boolean): ReadonlySignal<Value> | ReadonlyFrozenSignal<Value>;
3
+ export declare function getReadonlyInstance<Value>(this: InternalSignal, frozen?: never): ReadonlySignal<Value>;
4
+ export declare function getReadonlySignal(state: SignalState, frozen: boolean): ReadonlySignal<unknown> | ReadonlyFrozenSignal<unknown>;
5
5
  //#endregion
@@ -1,32 +1,39 @@
1
- import { NAME_MORA, NAME_READONLY, SUBSCRIPTION_TYPE_FROZEN, SUBSCRIPTION_TYPE_READONLY } from "../constants.mjs";
2
- import { getFrozenValue, getSimpleValue, peekSimpleValue } from "../helpers/value.mjs";
3
- import { subscribeToReactive } from "../helpers/subscription.mjs";
1
+ import { NAME_MORA, NAME_READONLY, SUBSCRIPTION_TYPE_FROZEN, SUBSCRIPTION_TYPE_ORIGINAL, SYMBOL_STATE } from "../constants.mjs";
2
+ import { getFrozenValue, getSignalValue, getStringValue, peekSignalValue } from "../helpers/value.mjs";
3
+ import { subscribeToReadonly } from "../helpers/subscription.mjs";
4
4
  //#region src/value/readonly.ts
5
- function getReadonlyInstance(state, instances, frozen) {
6
- const key = frozen ? "frozen" : "original";
7
- instances[key] ??= getReadonlySignal(state, frozen);
8
- return instances[key];
5
+ function Readonly(state, frozen) {
6
+ this[SYMBOL_STATE] = state;
7
+ Object.defineProperty(this, SUBSCRIPTION_TYPE_FROZEN, {
8
+ enumerable: true,
9
+ value: frozen
10
+ });
11
+ }
12
+ Readonly.prototype[NAME_MORA] = NAME_READONLY;
13
+ Readonly.prototype.subscribe = subscribeToReadonly;
14
+ Readonly.prototype.toString = getStringValue;
15
+ Readonly.prototype.get = function() {
16
+ return getReadonlyValue(this, false, this[SUBSCRIPTION_TYPE_FROZEN]);
17
+ };
18
+ Readonly.prototype.peek = function(copy) {
19
+ return getReadonlyValue(this, true, this[SUBSCRIPTION_TYPE_FROZEN], copy);
20
+ };
21
+ Readonly.prototype.toJSON = function() {
22
+ return this[SYMBOL_STATE].value;
23
+ };
24
+ function getReadonlyInstance(frozen) {
25
+ const key = frozen === true ? SUBSCRIPTION_TYPE_FROZEN : SUBSCRIPTION_TYPE_ORIGINAL;
26
+ this[SYMBOL_STATE].readonlies ??= {};
27
+ this[SYMBOL_STATE].readonlies[key] ??= getReadonlySignal(this[SYMBOL_STATE], frozen === true);
28
+ return this[SYMBOL_STATE].readonlies[key];
9
29
  }
10
30
  function getReadonlySignal(state, frozen) {
11
- const type = frozen ? SUBSCRIPTION_TYPE_FROZEN : SUBSCRIPTION_TYPE_READONLY;
12
- const instance = {
13
- get: () => getReadonlyValue(state, false, frozen),
14
- peek: (copy) => getReadonlyValue(state, true, frozen, copy),
15
- subscribe: (callback) => subscribeToReactive(type, state, callback)
16
- };
17
- Object.defineProperties(instance, {
18
- [NAME_MORA]: { value: NAME_READONLY },
19
- frozen: {
20
- enumerable: true,
21
- value: frozen
22
- }
23
- });
24
- return Object.freeze(instance);
31
+ return new Readonly(state, frozen);
25
32
  }
26
- function getReadonlyValue(state, peek, frozen, copy) {
33
+ function getReadonlyValue(instance, peek, frozen, copy) {
27
34
  let value;
28
- if (peek) value = peekSimpleValue(state.value, copy === true);
29
- else value = getSimpleValue(state);
35
+ if (peek) value = peekSignalValue.call(instance, copy === true);
36
+ else value = getSignalValue.call(instance);
30
37
  return frozen ? getFrozenValue(value) : value;
31
38
  }
32
39
  //#endregion
@@ -1,5 +1,6 @@
1
1
  import { ReactiveOptions, Signal } from "../models.mjs";
2
2
  //#region src/value/signal.d.ts
3
+ declare function Signal(this: any, value: unknown, options?: ReactiveOptions<unknown>): void;
3
4
  /**
4
5
  * Create a reactive value from a function result
5
6
  *
@@ -1,30 +1,37 @@
1
- import { NAME_MORA, NAME_SIGNAL } from "../constants.mjs";
2
- import { emitValue, getSimpleValue, handleSimpleValue, peekSimpleValue, updateSimpleValue } from "../helpers/value.mjs";
1
+ import { NAME_MORA, NAME_SIGNAL, SYMBOL_STATE } from "../constants.mjs";
2
+ import { emitValue, getSignalValue, getStringValue, handleSignalValue, peekSignalValue, updateSignalValue } from "../helpers/value.mjs";
3
+ import { getState } from "../helpers/misc.mjs";
3
4
  import { subscribeToSignal } from "../helpers/subscription.mjs";
4
- import { reactive } from "./reactive.mjs";
5
5
  import { getReadonlyInstance } from "./readonly.mjs";
6
6
  //#region src/value/signal.ts
7
- function setAndEmit(state, value) {
8
- if (!state.equal(state.value, value)) {
7
+ function Signal(value, options) {
8
+ this[SYMBOL_STATE] = getState(void 0, options);
9
+ handleSignalValue(this, value, setAndEmit);
10
+ }
11
+ Signal.prototype[NAME_MORA] = NAME_SIGNAL;
12
+ Signal.prototype.asReadonly = getReadonlyInstance;
13
+ Signal.prototype.get = getSignalValue;
14
+ Signal.prototype.peek = peekSignalValue;
15
+ Signal.prototype.subscribe = subscribeToSignal;
16
+ Signal.prototype.toString = getStringValue;
17
+ Signal.prototype.set = function(value) {
18
+ return handleSignalValue(this, value, setAndEmit);
19
+ };
20
+ Signal.prototype.toJSON = function() {
21
+ return this[SYMBOL_STATE].value;
22
+ };
23
+ Signal.prototype.update = function(callback) {
24
+ return updateSignalValue(this, callback, setAndEmit);
25
+ };
26
+ function setAndEmit(instance, value) {
27
+ const state = instance[SYMBOL_STATE];
28
+ if (!(state.equal ?? Object.is)(state.value, value)) {
9
29
  state.value = value;
10
- emitValue(state);
30
+ emitValue(instance);
11
31
  }
12
32
  }
13
33
  function signal(value, options) {
14
- const [rx, state] = reactive(void 0, options);
15
- const readonlies = {};
16
- const instance = {
17
- ...rx,
18
- asReadonly: (frozen) => getReadonlyInstance(state, readonlies, frozen === true),
19
- get: () => getSimpleValue(state),
20
- peek: (copy) => peekSimpleValue(state.value, copy === true),
21
- set: (value) => handleSimpleValue(state, value, setAndEmit),
22
- subscribe: (callback, copy) => subscribeToSignal(state, callback, copy === true),
23
- update: (callback) => updateSimpleValue(state, callback, setAndEmit)
24
- };
25
- Object.defineProperty(instance, NAME_MORA, { value: NAME_SIGNAL });
26
- handleSimpleValue(state, value, setAndEmit);
27
- return Object.freeze(instance);
34
+ return new Signal(value, options);
28
35
  }
29
36
  //#endregion
30
37
  export { signal };
@@ -1,6 +1,7 @@
1
1
  import { ReactiveOptions, ReactiveStore } from "../models.mjs";
2
2
  import { PlainObject } from "@oscarpalmer/atoms/models";
3
3
  //#region src/value/store.d.ts
4
+ declare function ReactiveStore(this: any, value: never, options?: ReactiveOptions<unknown>): void;
4
5
  /**
5
6
  * Create a reactive store from a function result
6
7
  *
@@ -1,50 +1,32 @@
1
- import { NAME_MORA, NAME_STORE } from "../constants.mjs";
2
- import { startBatch, stopBatch } from "../batch.mjs";
1
+ import { NAME_MORA, NAME_STORE, SYMBOL_STATE } from "../constants.mjs";
2
+ import { getStringValue } from "../helpers/value.mjs";
3
+ import { getState } from "../helpers/misc.mjs";
3
4
  import { subscribeToProxy } from "../helpers/subscription.mjs";
4
- import { reactive } from "./reactive.mjs";
5
5
  import { emitProxyValues, getValueInProxy, peekValueInProxy, setProxyValue, setValueInProxy, updateProxyValue } from "../helpers/proxy.mjs";
6
6
  import { getReadonlyInstance } from "./readonly.mjs";
7
7
  //#region src/value/store.ts
8
- function setPropertyValue(state, key, value) {
9
- state.value[key] = value;
10
- }
11
- function setStoreValue(state, value) {
12
- startBatch();
13
- const actual = value ?? {};
14
- const proxy = state.value;
15
- const proxyKeys = Object.keys(proxy);
16
- const actualKeys = Object.keys(actual);
17
- let { length } = proxyKeys;
18
- for (let index = 0; index < length; index += 1) {
19
- const key = proxyKeys[index];
20
- proxy[key] = actualKeys.includes(key) ? actual[key] : void 0;
21
- }
22
- length = actualKeys.length;
23
- for (let index = 0; index < length; index += 1) {
24
- const key = actualKeys[index];
25
- if (!proxyKeys.includes(key)) proxy[key] = actual[key];
26
- }
27
- stopBatch();
8
+ function ReactiveStore(value, options) {
9
+ this[SYMBOL_STATE] = {
10
+ ...getState(void 0, options),
11
+ isArray: false
12
+ };
13
+ this[SYMBOL_STATE].value = new Proxy({}, { set: (target, property, value) => setValueInProxy(this, target, property, value) });
14
+ setProxyValue.call(this, value);
28
15
  }
16
+ ReactiveStore.prototype[NAME_MORA] = NAME_STORE;
17
+ ReactiveStore.prototype.asReadonly = getReadonlyInstance;
18
+ ReactiveStore.prototype.get = getValueInProxy;
19
+ ReactiveStore.prototype.notify = emitProxyValues;
20
+ ReactiveStore.prototype.peek = peekValueInProxy;
21
+ ReactiveStore.prototype.set = setProxyValue;
22
+ ReactiveStore.prototype.subscribe = subscribeToProxy;
23
+ ReactiveStore.prototype.toString = getStringValue;
24
+ ReactiveStore.prototype.update = updateProxyValue;
25
+ ReactiveStore.prototype.toJSON = function() {
26
+ return this[SYMBOL_STATE].value;
27
+ };
29
28
  function store(value, options) {
30
- const [rx, state] = reactive(void 0, options);
31
- const isArray = false;
32
- const keyed = /* @__PURE__ */ new Map();
33
- const readonlies = {};
34
- state.value = new Proxy({}, { set: (target, property, value) => setValueInProxy(isArray, state, target, property, value) });
35
- const instance = {
36
- ...rx,
37
- asReadonly: (frozen) => getReadonlyInstance(state, readonlies, frozen === true),
38
- get: (value) => getValueInProxy(isArray, instance, state, keyed, value),
39
- notify: () => emitProxyValues(state, keyed),
40
- peek: (first, second) => peekValueInProxy(isArray, state, first, second),
41
- set: (first, second) => setProxyValue(isArray, state, setStoreValue, setPropertyValue, first, second),
42
- subscribe: (first, second, third) => subscribeToProxy(isArray, instance, state, keyed, first, second, third),
43
- update: (callback) => updateProxyValue(isArray, state, setStoreValue, setPropertyValue, callback)
44
- };
45
- Object.defineProperty(instance, NAME_MORA, { value: NAME_STORE });
46
- instance.set(value);
47
- return Object.freeze(instance);
29
+ return new ReactiveStore(value, options);
48
30
  }
49
31
  //#endregion
50
32
  export { store };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/mora",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "description": "Signals and stuff…",
5
5
  "keywords": [
6
6
  "reactive",
package/src/batch.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {BATCH} from './constants';
2
2
  import {runEffect} from './effect';
3
- import {getFrozenValue, peekSimpleValue} from './helpers/value';
3
+ import {getFrozenValue, peekSignalValue} from './helpers/value';
4
4
  import type {EffectState, StoredSubscription} from './models';
5
5
 
6
6
  // #region Functions
@@ -27,7 +27,7 @@ export function flushHandlers(): void {
27
27
  subscription.callback(
28
28
  subscription.frozen
29
29
  ? getFrozenValue(subscription.state.value)
30
- : peekSimpleValue(subscription.state.value, subscription.copy),
30
+ : peekSignalValue.call(subscription.instance, subscription.copy),
31
31
  );
32
32
  } else {
33
33
  runEffect(handler as EffectState);
package/src/constants.ts CHANGED
@@ -53,6 +53,10 @@ export const NAME_ALL = new Set([
53
53
 
54
54
  export const PROPERTY_LENGTH = 'length';
55
55
 
56
+ export const SYMBOL_EFFECT = Symbol('effect');
57
+
58
+ export const SYMBOL_STATE = Symbol('state');
59
+
56
60
  export const SUBSCRIPTION_PROPERTY = {
57
61
  key: NAME_MORA,
58
62
  value: NAME_SUBSCRIPTION,
package/src/effect.ts CHANGED
@@ -1,7 +1,19 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import {ACTIVE, NAME_EFFECT, NAME_MORA} from './constants';
2
+ import {ACTIVE, NAME_EFFECT, NAME_MORA, SYMBOL_STATE} from './constants';
3
3
  import type {Effect, EffectState} from './models';
4
4
 
5
+ // #region Instance
6
+
7
+ function Effect(this: any, callback: GenericCallback) {
8
+ this[SYMBOL_STATE] = {callback};
9
+
10
+ runEffect(this[SYMBOL_STATE]);
11
+ }
12
+
13
+ Effect.prototype[NAME_MORA] = NAME_EFFECT;
14
+
15
+ // #endregion
16
+
5
17
  // #region Functions
6
18
 
7
19
  /**
@@ -19,17 +31,10 @@ export function effect(callback: GenericCallback): Effect {
19
31
  }
20
32
 
21
33
  function getEffect(callback: GenericCallback): [Effect, EffectState] {
22
- const state: EffectState = {
23
- callback,
24
- };
25
-
26
- const instance = Object.freeze({
27
- [NAME_MORA]: NAME_EFFECT,
28
- });
29
-
30
- runEffect(state);
34
+ // @ts-expect-error All good, no worries :-)
35
+ const instance = new Effect(callback);
31
36
 
32
- return [instance, state];
37
+ return [instance, instance[SYMBOL_STATE]];
33
38
  }
34
39
 
35
40
  export function internalEffect(callback: GenericCallback): EffectState {
package/src/helpers/is.ts CHANGED
@@ -27,7 +27,7 @@ import type {
27
27
  * @param value Value to check
28
28
  * @returns True if value is a {@link Computed}
29
29
  */
30
- export function isComputed<Value>(value: unknown): value is Computed<Value> {
30
+ export function isComputed<Value = unknown>(value: unknown): value is Computed<Value> {
31
31
  return isMora<Computed<Value>>(value, NAME_COMPUTED);
32
32
  }
33
33
 
@@ -58,7 +58,7 @@ function isMora<Instance>(value: unknown, name: string | Set<string>): value is
58
58
  * @param value Value to check
59
59
  * @returns True if value is a {@link Reactive}
60
60
  */
61
- export function isReactive<Value>(value: unknown): value is Reactive<Value> {
61
+ export function isReactive<Value = unknown>(value: unknown): value is Reactive<Value> {
62
62
  return isMora<Reactive<Value>>(value, NAME_ALL);
63
63
  }
64
64
 
@@ -68,7 +68,7 @@ export function isReactive<Value>(value: unknown): value is Reactive<Value> {
68
68
  * @param value Value to check
69
69
  * @returns True if value is a {@link Signal}
70
70
  */
71
- export function isSignal<Value>(value: unknown): value is Signal<Value> {
71
+ export function isSignal<Value = unknown>(value: unknown): value is Signal<Value> {
72
72
  return isMora<Signal<Value>>(value, NAME_SIGNAL);
73
73
  }
74
74
 
@@ -78,7 +78,7 @@ export function isSignal<Value>(value: unknown): value is Signal<Value> {
78
78
  * @param value Value to check
79
79
  * @returns True if value is a {@link ReactiveArray}
80
80
  */
81
- export function isReactiveArray<Item>(value: unknown): value is ReactiveArray<Item> {
81
+ export function isReactiveArray<Item = unknown>(value: unknown): value is ReactiveArray<Item> {
82
82
  return isMora<ReactiveArray<Item>>(value, NAME_ARRAY);
83
83
  }
84
84
 
@@ -88,7 +88,7 @@ export function isReactiveArray<Item>(value: unknown): value is ReactiveArray<It
88
88
  * @param value Value to check
89
89
  * @returns True if value is a {@link Store}
90
90
  */
91
- export function isReactiveStore<Value extends PlainObject>(
91
+ export function isReactiveStore<Value extends PlainObject = PlainObject>(
92
92
  value: unknown,
93
93
  ): value is ReactiveStore<Value> {
94
94
  return isMora<ReactiveStore<Value>>(value, NAME_STORE);
@@ -100,7 +100,7 @@ export function isReactiveStore<Value extends PlainObject>(
100
100
  * @param value Value to check
101
101
  * @returns True if value is a {@link ReadonlySignal}
102
102
  */
103
- export function isReadonlySignal<Value>(value: unknown): value is ReadonlySignal<Value> {
103
+ export function isReadonlySignal<Value = unknown>(value: unknown): value is ReadonlySignal<Value> {
104
104
  return isMora<ReadonlySignal<Value>>(value, NAME_READONLY);
105
105
  }
106
106
 
@@ -0,0 +1,15 @@
1
+ import type {ReactiveOptions, ReactiveState} from '../models';
2
+
3
+ // #region Functions
4
+
5
+ export function getState(value: unknown, options?: ReactiveOptions<unknown>): ReactiveState {
6
+ return {
7
+ value,
8
+ equal:
9
+ typeof options === 'object' && typeof options?.equal === 'function'
10
+ ? options.equal
11
+ : undefined,
12
+ };
13
+ }
14
+
15
+ // #endregion