@oscarpalmer/mora 0.30.0 → 0.32.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 (55) hide show
  1. package/dist/batch.d.mts +4 -5
  2. package/dist/batch.mjs +4 -2
  3. package/dist/constants.d.mts +29 -18
  4. package/dist/constants.mjs +19 -2
  5. package/dist/effect.d.mts +4 -5
  6. package/dist/effect.mjs +2 -2
  7. package/dist/helpers/is.d.mts +21 -15
  8. package/dist/helpers/is.mjs +21 -12
  9. package/dist/helpers/proxy.d.mts +12 -10
  10. package/dist/helpers/proxy.mjs +32 -14
  11. package/dist/helpers/subscription.d.mts +8 -0
  12. package/dist/helpers/subscription.mjs +27 -0
  13. package/dist/helpers/value.d.mts +8 -6
  14. package/dist/helpers/value.mjs +30 -4
  15. package/dist/index.d.mts +4 -3
  16. package/dist/index.mjs +2 -2
  17. package/dist/models.d.mts +108 -84
  18. package/dist/mora.full.mjs +504 -247
  19. package/dist/value/array.d.mts +4 -5
  20. package/dist/value/array.mjs +25 -56
  21. package/dist/value/computed.d.mts +3 -4
  22. package/dist/value/computed.mjs +14 -14
  23. package/dist/value/reactive.d.mts +2 -3
  24. package/dist/value/reactive.mjs +0 -1
  25. package/dist/value/readonly.d.mts +5 -0
  26. package/dist/value/readonly.mjs +33 -0
  27. package/dist/value/signal.d.mts +4 -5
  28. package/dist/value/signal.mjs +10 -17
  29. package/dist/value/store.d.mts +4 -5
  30. package/dist/value/store.mjs +17 -41
  31. package/package.json +6 -6
  32. package/src/batch.ts +15 -5
  33. package/src/constants.ts +41 -3
  34. package/src/effect.ts +6 -2
  35. package/src/helpers/is.ts +37 -12
  36. package/src/helpers/proxy.ts +99 -49
  37. package/src/helpers/subscription.ts +78 -0
  38. package/src/helpers/value.ts +69 -4
  39. package/src/index.ts +18 -2
  40. package/src/models.ts +131 -86
  41. package/src/value/array.ts +67 -120
  42. package/src/value/computed.ts +36 -13
  43. package/src/value/reactive.ts +8 -5
  44. package/src/value/readonly.ts +72 -0
  45. package/src/value/signal.ts +22 -16
  46. package/src/value/store.ts +37 -70
  47. package/types/constants.d.ts +1 -1
  48. package/types/index.d.ts +1 -1
  49. package/types/value/array.d.ts +1 -1
  50. package/types/value/computed.d.ts +0 -3
  51. package/types/value/signal.d.ts +1 -1
  52. package/types/value/store.d.ts +1 -1
  53. package/dist/subscription.d.mts +0 -7
  54. package/dist/subscription.mjs +0 -27
  55. package/src/subscription.ts +0 -45
@@ -7,7 +7,7 @@ import { ReactiveArray, ReactiveOptions } from "../models.mjs";
7
7
  * @param options Reactivity options
8
8
  * @returns Reactive array
9
9
  */
10
- declare function array<Item>(value: () => Item[] | Promise<Item[]>, options?: ReactiveOptions<Item>): ReactiveArray<Item>;
10
+ export declare function array<Item>(value: () => Item[] | Promise<Item[]>, options?: ReactiveOptions<Item>): ReactiveArray<Item>;
11
11
  /**
12
12
  * Create a reactive array from a promise
13
13
  *
@@ -15,7 +15,7 @@ declare function array<Item>(value: () => Item[] | Promise<Item[]>, options?: Re
15
15
  * @param options Reactivity options
16
16
  * @returns Reactive array
17
17
  */
18
- declare function array<Item>(value: Promise<Item[]>, options?: ReactiveOptions<Item>): ReactiveArray<Item>;
18
+ export declare function array<Item>(value: Promise<Item[]>, options?: ReactiveOptions<Item>): ReactiveArray<Item>;
19
19
  /**
20
20
  * Create a reactive array
21
21
  *
@@ -23,6 +23,5 @@ declare function array<Item>(value: Promise<Item[]>, options?: ReactiveOptions<I
23
23
  * @param options Reactivity options
24
24
  * @returns Reactive array
25
25
  */
26
- declare function array<Item>(value: Item[], options?: ReactiveOptions<Item>): ReactiveArray<Item>;
27
- //#endregion
28
- export { array };
26
+ export declare function array<Item>(value: Item[], options?: ReactiveOptions<Item>): ReactiveArray<Item>;
27
+ //#endregion
@@ -1,35 +1,33 @@
1
1
  import { METHODS_AFFECTING_LENGTH, METHODS_UPDATE, NAME_ARRAY, NAME_MORA } from "../constants.mjs";
2
- import { emitValue, equalArrays, getSimpleValue } from "../helpers/value.mjs";
3
- import { subscribe, unsubscribe } from "../subscription.mjs";
2
+ import { emitValue, equalArrays } from "../helpers/value.mjs";
3
+ import { subscribeToProxy } from "../helpers/subscription.mjs";
4
4
  import { reactive } from "./reactive.mjs";
5
5
  import { computed } from "./computed.mjs";
6
- import { emityProxyValues, getReactiveValueInProxy, setProxyValue, setValueInProxy } from "../helpers/proxy.mjs";
6
+ import { emitProxyValues, getValueInProxy, peekValueInProxy, setProxyValue, setValueInProxy, updateProxyValue } from "../helpers/proxy.mjs";
7
+ import { getReadonlyInstance } from "./readonly.mjs";
7
8
  import { signal } from "./signal.mjs";
8
9
  import { select } from "@oscarpalmer/atoms/array";
9
10
  import { filter } from "@oscarpalmer/atoms/array/filter";
10
- import { noop } from "@oscarpalmer/atoms/function";
11
11
  //#region src/value/array.ts
12
12
  function array(value, options) {
13
13
  const [rx, state] = reactive([], options);
14
- const indiced = /* @__PURE__ */ new Map();
14
+ const isArray = true;
15
15
  const length = signal(0);
16
+ const mapped = /* @__PURE__ */ new Map();
17
+ const readonlies = {};
16
18
  state.value = new Proxy([], {
17
19
  get: (target, property) => METHODS_UPDATE.has(property) ? updateArray(property, target, state, length) : Reflect.get(target, property),
18
- set: (target, property, value) => setValueInProxy({
19
- length,
20
- property,
21
- state,
22
- target,
23
- value,
24
- isArray: true
25
- })
20
+ set: (target, property, value) => setValueInProxy(isArray, state, target, property, value, length)
26
21
  });
27
22
  function get(value) {
28
- return getArrayValue(instance, indiced, state, length, 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);
29
27
  }
30
28
  const instance = {
31
29
  ...rx,
32
- length: state.value.length,
30
+ asReadonly: (frozen) => getReadonlyInstance(state, readonlies, frozen === true),
33
31
  at: (index) => get(index),
34
32
  clear: () => {
35
33
  state.value.length = 0;
@@ -37,63 +35,38 @@ function array(value, options) {
37
35
  filter: (callback) => computed(() => filter(get(), callback)),
38
36
  get: (value) => get(value),
39
37
  map: (callback) => computed(() => get().map(callback)),
40
- notify: () => {
41
- emityProxyValues(state, indiced);
42
- },
43
- peek: (value) => peekArrayValue(state, length, value),
38
+ notify: () => emitProxyValues(state, mapped),
39
+ peek: (first, second) => peekArrayValue(state, length, first, second),
44
40
  pop: () => state.value.pop(),
45
41
  push: (...items) => state.value.push(...items),
46
42
  select: (filter, map) => computed(() => select(get(), filter, map)),
47
- set: (first, second) => {
48
- setProxyValue(true, state, isArrayValue, isArrayIndex, setArray, setAtIndex, first, second);
49
- },
43
+ set: (first, second) => set(first, second),
50
44
  shift: () => state.value.shift(),
51
45
  splice: (from, to, ...items) => state.value.splice(from, to ?? state.value.length, ...items),
52
- subscribe: (first, second) => {
53
- if (typeof first === "number" && typeof second === "function") return getReactiveValueInProxy(instance, indiced, first, true).subscribe(second);
54
- return typeof first === "function" ? subscribe(state, first) : noop;
55
- },
46
+ subscribe: (first, second, third) => subscribeToProxy(isArray, instance, state, mapped, first, second, third),
56
47
  unshift: (...items) => state.value.unshift(...items),
57
- unsubscribe: (first, second) => {
58
- if (typeof first === "number" && typeof second === "function") getReactiveValueInProxy(instance, indiced, first, true)?.unsubscribe(second);
59
- else if (typeof first === "function") unsubscribe(state, first);
60
- },
61
- update: (callback) => updateArrayValue(instance, state, callback)
48
+ update: (callback) => updateProxyValue(true, state, setArrayValue, setAtIndex, callback)
62
49
  };
63
50
  Object.defineProperties(instance, {
64
- [NAME_MORA]: {
65
- enumerable: false,
66
- value: NAME_ARRAY
67
- },
51
+ [NAME_MORA]: { value: NAME_ARRAY },
68
52
  length: {
69
53
  enumerable: true,
70
54
  get: () => length.get(),
71
55
  set: (value) => setArrayLength(state, value)
72
56
  }
73
57
  });
74
- instance.set(value);
58
+ set(value);
75
59
  return Object.freeze(instance);
76
60
  }
77
- function getArrayValue(instance, indiced, state, length, first) {
78
- if (typeof first === "number") return getReactiveValueInProxy(instance, indiced, first, true).get();
79
- return first === "length" ? length.get() : getSimpleValue(state);
61
+ function peekArrayValue(state, length, first, second) {
62
+ return first === "length" ? length.peek() : peekValueInProxy(true, state, first, second);
80
63
  }
81
- function isArrayIndex(value) {
82
- return typeof value === "number";
83
- }
84
- function isArrayValue(value) {
85
- return value == null || Array.isArray(value);
86
- }
87
- function peekArrayValue(state, length, value) {
88
- if (value === "length") return length.peek();
89
- return typeof value === "number" ? state.value.at(value) : state.value.slice();
64
+ function setArrayLength(state, value) {
65
+ if (typeof value === "number" && !Number.isNaN(value) && value >= 0 && value !== state.value.length) state.value.length = value;
90
66
  }
91
- function setArray(state, value) {
67
+ function setArrayValue(state, value) {
92
68
  state.value.splice(0, state.value.length, ...value ?? []);
93
69
  }
94
- function setArrayLength(state, value) {
95
- if (typeof value === "number" && value >= 0 && value !== state.value.length) state.value.length = value;
96
- }
97
70
  function setAtIndex(state, index, value) {
98
71
  const actual = index < 0 ? state.value.length + index : index;
99
72
  if (actual > -1) state.value[actual] = value;
@@ -111,9 +84,5 @@ function updateArray(type, array, state, length) {
111
84
  return result;
112
85
  };
113
86
  }
114
- function updateArrayValue(instance, state, callback) {
115
- const updated = callback(state.value);
116
- if (updated == null || Array.isArray(updated)) instance.set(updated);
117
- }
118
87
  //#endregion
119
88
  export { array };
@@ -7,7 +7,6 @@ import { Computed, ComputedEffect, ReactiveOptions } from "../models.mjs";
7
7
  * @param options Reactivity options
8
8
  * @returns Computed value
9
9
  */
10
- declare function computed<Value>(callback: () => Value | Promise<Value>, options?: ReactiveOptions<Value>): Computed<Value>;
11
- declare function internalComputed<Value>(callback: () => Value | Promise<Value>, options?: ReactiveOptions<Value>): [Computed<Value>, ComputedEffect];
12
- //#endregion
13
- export { computed, internalComputed };
10
+ 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];
12
+ //#endregion
@@ -1,8 +1,8 @@
1
- import { ACTIVE, BATCH, NAME_COMPUTED, NAME_MORA } from "../constants.mjs";
1
+ import { ACTIVE, BATCH, NAME_COMPUTED, NAME_MORA, SUBSCRIPTION_TYPES, SUBSCRIPTION_TYPES_COPY } from "../constants.mjs";
2
2
  import { internalEffect, runEffect } from "../effect.mjs";
3
+ import { handleSimpleValue, peekSimpleValue } from "../helpers/value.mjs";
3
4
  import { flushHandlers } from "../batch.mjs";
4
- import { handleSimpleValue } from "../helpers/value.mjs";
5
- import { subscribe } from "../subscription.mjs";
5
+ import { subscribeToSignal } from "../helpers/subscription.mjs";
6
6
  import { reactive } from "./reactive.mjs";
7
7
  //#region src/value/computed.ts
8
8
  /**
@@ -16,21 +16,16 @@ function computed(callback, options) {
16
16
  return getComputed(callback, options)[0];
17
17
  }
18
18
  function getComputed(callback, options) {
19
+ if (typeof callback !== "function") throw new TypeError("Computed callback must be a function");
19
20
  const [rx, state] = reactive(void 0, options);
20
21
  let fx;
21
22
  const instance = {
22
23
  ...rx,
23
24
  get: () => getValue(state, fx),
24
- peek: () => state.value,
25
- subscribe: (callback) => subscribe(state, callback),
26
- unsubscribe: (callback) => {
27
- state.subscriptions.delete(callback);
28
- }
25
+ peek: (copy) => peekSimpleValue(state.value, copy === true),
26
+ subscribe: (callback, copy) => subscribeToSignal(state, callback, copy === true)
29
27
  };
30
- Object.defineProperty(instance, NAME_MORA, {
31
- enumerable: false,
32
- value: NAME_COMPUTED
33
- });
28
+ Object.defineProperty(instance, NAME_MORA, { value: NAME_COMPUTED });
34
29
  fx = getComputedEffect(state, callback);
35
30
  return [Object.freeze(instance), fx];
36
31
  }
@@ -64,8 +59,13 @@ function setAndEmit(state, value) {
64
59
  if (state.equal(state.value, value)) return;
65
60
  state.value = value;
66
61
  for (const computed of state.computeds) computed.dirty = true;
67
- for (const effect of state.effects) BATCH.handlers.add(effect);
68
- for (const [, subscription] of state.subscriptions) subscription.callback(value);
62
+ for (const effect of state.effects) BATCH.handlers.set(effect, effect);
63
+ for (const type of SUBSCRIPTION_TYPES) {
64
+ if (type === "frozen") continue;
65
+ const copy = SUBSCRIPTION_TYPES_COPY.has(type);
66
+ const subscriptions = state.subscriptions?.values.to.keyed?.get(type);
67
+ if (subscriptions != null) for (const [, callback] of subscriptions) callback(peekSimpleValue(state.value, copy));
68
+ }
69
69
  flushHandlers();
70
70
  }
71
71
  //#endregion
@@ -1,5 +1,4 @@
1
1
  import { Reactive, ReactiveOptions, ReactiveState } from "../models.mjs";
2
2
  //#region src/value/reactive.d.ts
3
- declare function reactive<Value, Equal = Value>(value: Value, options?: ReactiveOptions<Equal>): [Reactive<Value>, ReactiveState<Value, Equal>];
4
- //#endregion
5
- export { reactive };
3
+ export declare function reactive<Value, Item = Value>(value: Value, options?: ReactiveOptions<Item>): [Reactive<Value>, ReactiveState<Value, Item>];
4
+ //#endregion
@@ -4,7 +4,6 @@ function reactive(value, options) {
4
4
  computeds: /* @__PURE__ */ new Set(),
5
5
  effects: /* @__PURE__ */ new Set(),
6
6
  equal: typeof options === "object" && typeof options?.equal === "function" ? options.equal : Object.is,
7
- subscriptions: /* @__PURE__ */ new Map(),
8
7
  value
9
8
  };
10
9
  return [{
@@ -0,0 +1,5 @@
1
+ import { ReactiveState, ReadonlyFrozenSignal, ReadonlyInstances, ReadonlySignal } from "../models.mjs";
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>;
5
+ //#endregion
@@ -0,0 +1,33 @@
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";
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];
9
+ }
10
+ 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);
25
+ }
26
+ function getReadonlyValue(state, peek, frozen, copy) {
27
+ let value;
28
+ if (peek) value = peekSimpleValue(state.value, copy === true);
29
+ else value = getSimpleValue(state);
30
+ return frozen ? getFrozenValue(value) : value;
31
+ }
32
+ //#endregion
33
+ export { getReadonlyInstance, getReadonlySignal };
@@ -7,7 +7,7 @@ import { ReactiveOptions, Signal } from "../models.mjs";
7
7
  * @param options Reactivity options
8
8
  * @returns Reactive value
9
9
  */
10
- declare function signal<Value>(value: () => Value | Promise<Value>, options?: ReactiveOptions<Value>): Signal<Value>;
10
+ export declare function signal<Value>(value: () => Value | Promise<Value>, options?: ReactiveOptions<Value>): Signal<Value>;
11
11
  /**
12
12
  * Create a reactive value from a promise
13
13
  *
@@ -15,7 +15,7 @@ declare function signal<Value>(value: () => Value | Promise<Value>, options?: Re
15
15
  * @param options Reactivity options
16
16
  * @returns Reactive value
17
17
  */
18
- declare function signal<Value>(value: Promise<Value>, options?: ReactiveOptions<Value>): Signal<Value>;
18
+ export declare function signal<Value>(value: Promise<Value>, options?: ReactiveOptions<Value>): Signal<Value>;
19
19
  /**
20
20
  * Create a reactive value
21
21
  *
@@ -23,6 +23,5 @@ declare function signal<Value>(value: Promise<Value>, options?: ReactiveOptions<
23
23
  * @param options Reactivity options
24
24
  * @returns Reactive value
25
25
  */
26
- declare function signal<Value>(value: Value, options?: ReactiveOptions<Value>): Signal<Value>;
27
- //#endregion
28
- export { signal };
26
+ export declare function signal<Value>(value: Value, options?: ReactiveOptions<Value>): Signal<Value>;
27
+ //#endregion
@@ -1,7 +1,8 @@
1
1
  import { NAME_MORA, NAME_SIGNAL } from "../constants.mjs";
2
- import { emitValue, getSimpleValue, handleSimpleValue } from "../helpers/value.mjs";
3
- import { subscribe } from "../subscription.mjs";
2
+ import { emitValue, getSimpleValue, handleSimpleValue, peekSimpleValue, updateSimpleValue } from "../helpers/value.mjs";
3
+ import { subscribeToSignal } from "../helpers/subscription.mjs";
4
4
  import { reactive } from "./reactive.mjs";
5
+ import { getReadonlyInstance } from "./readonly.mjs";
5
6
  //#region src/value/signal.ts
6
7
  function setAndEmit(state, value) {
7
8
  if (!state.equal(state.value, value)) {
@@ -11,25 +12,17 @@ function setAndEmit(state, value) {
11
12
  }
12
13
  function signal(value, options) {
13
14
  const [rx, state] = reactive(void 0, options);
15
+ const readonlies = {};
14
16
  const instance = {
15
17
  ...rx,
18
+ asReadonly: (frozen) => getReadonlyInstance(state, readonlies, frozen === true),
16
19
  get: () => getSimpleValue(state),
17
- peek: () => state.value,
18
- set: (value) => {
19
- handleSimpleValue(state, value, setAndEmit);
20
- },
21
- update: (callback) => {
22
- handleSimpleValue(state, callback(state.value), setAndEmit);
23
- },
24
- subscribe: (callback) => subscribe(state, callback),
25
- unsubscribe: (callback) => {
26
- state.subscriptions.delete(callback);
27
- }
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)
28
24
  };
29
- Object.defineProperty(instance, NAME_MORA, {
30
- enumerable: false,
31
- value: NAME_SIGNAL
32
- });
25
+ Object.defineProperty(instance, NAME_MORA, { value: NAME_SIGNAL });
33
26
  handleSimpleValue(state, value, setAndEmit);
34
27
  return Object.freeze(instance);
35
28
  }
@@ -8,7 +8,7 @@ import { PlainObject } from "@oscarpalmer/atoms/models";
8
8
  * @param options Reactivity options
9
9
  * @returns Reactive store
10
10
  */
11
- declare function store<Value extends PlainObject>(value: () => Value | Promise<Value>, options?: ReactiveOptions<Value>): ReactiveStore<Value>;
11
+ export declare function store<Value extends PlainObject>(value: () => Value | Promise<Value>, options?: ReactiveOptions<Value>): ReactiveStore<Value>;
12
12
  /**
13
13
  * Create a reactive store from a promise
14
14
  *
@@ -16,7 +16,7 @@ declare function store<Value extends PlainObject>(value: () => Value | Promise<V
16
16
  * @param options Reactivity options
17
17
  * @returns Reactive store
18
18
  */
19
- declare function store<Value extends PlainObject>(value: Promise<Value>, options?: ReactiveOptions<Value>): ReactiveStore<Value>;
19
+ export declare function store<Value extends PlainObject>(value: Promise<Value>, options?: ReactiveOptions<Value>): ReactiveStore<Value>;
20
20
  /**
21
21
  * Create a reactive store
22
22
  *
@@ -24,6 +24,5 @@ declare function store<Value extends PlainObject>(value: Promise<Value>, options
24
24
  * @param options Reactivity options
25
25
  * @returns Reactive store
26
26
  */
27
- declare function store<Value extends PlainObject>(value: Value, options?: ReactiveOptions<Value>): ReactiveStore<Value>;
28
- //#endregion
29
- export { store };
27
+ export declare function store<Value extends PlainObject>(value: Value, options?: ReactiveOptions<Value>): ReactiveStore<Value>;
28
+ //#endregion
@@ -1,15 +1,14 @@
1
1
  import { NAME_MORA, NAME_STORE } from "../constants.mjs";
2
2
  import { startBatch, stopBatch } from "../batch.mjs";
3
- import { getSimpleValue } from "../helpers/value.mjs";
4
- import { noop, subscribe, unsubscribe } from "../subscription.mjs";
3
+ import { subscribeToProxy } from "../helpers/subscription.mjs";
5
4
  import { reactive } from "./reactive.mjs";
6
- import { emityProxyValues, getReactiveValueInProxy, setProxyValue, setValueInProxy } from "../helpers/proxy.mjs";
7
- import { isKey, isPlainObject } from "@oscarpalmer/atoms/is";
5
+ import { emitProxyValues, getValueInProxy, peekValueInProxy, setProxyValue, setValueInProxy, updateProxyValue } from "../helpers/proxy.mjs";
6
+ import { getReadonlyInstance } from "./readonly.mjs";
8
7
  //#region src/value/store.ts
9
- function isStoreObject(value) {
10
- return value == null || isPlainObject(value);
8
+ function setPropertyValue(state, key, value) {
9
+ state.value[key] = value;
11
10
  }
12
- function setObject(state, value) {
11
+ function setStoreValue(state, value) {
13
12
  startBatch();
14
13
  const actual = value ?? {};
15
14
  const proxy = state.value;
@@ -27,46 +26,23 @@ function setObject(state, value) {
27
26
  }
28
27
  stopBatch();
29
28
  }
30
- function setProperty(state, key, value) {
31
- state.value[key] = value;
32
- }
33
29
  function store(value, options) {
34
30
  const [rx, state] = reactive(void 0, options);
31
+ const isArray = false;
35
32
  const keyed = /* @__PURE__ */ new Map();
36
- state.value = new Proxy({}, { set: (target, property, value) => setValueInProxy({
37
- target,
38
- property,
39
- state,
40
- value,
41
- isArray: false
42
- }) });
33
+ const readonlies = {};
34
+ state.value = new Proxy({}, { set: (target, property, value) => setValueInProxy(isArray, state, target, property, value) });
43
35
  const instance = {
44
36
  ...rx,
45
- get: (value) => isKey(value) ? getReactiveValueInProxy(instance, keyed, value, false).get() : getSimpleValue(state),
46
- notify() {
47
- emityProxyValues(state, keyed);
48
- },
49
- peek: (value) => isKey(value) ? state.value[value] : { ...state.value },
50
- set: (first, second) => {
51
- setProxyValue(false, state, isStoreObject, isKey, setObject, setProperty, first, second);
52
- },
53
- subscribe: (first, second) => {
54
- if (isKey(first) && typeof second === "function") return getReactiveValueInProxy(instance, keyed, first, false).subscribe(second);
55
- return typeof first === "function" ? subscribe(state, first) : noop;
56
- },
57
- unsubscribe: (first, second) => {
58
- if (isKey(first) && typeof second === "function") getReactiveValueInProxy(instance, keyed, first, false)?.unsubscribe(second);
59
- else if (typeof first === "function") unsubscribe(state, first);
60
- },
61
- update: (callback) => {
62
- const updated = callback(state.value);
63
- if (updated == null || isPlainObject(updated)) setObject(state, updated);
64
- }
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)
65
44
  };
66
- Object.defineProperty(instance, NAME_MORA, {
67
- enumerable: false,
68
- value: NAME_STORE
69
- });
45
+ Object.defineProperty(instance, NAME_MORA, { value: NAME_STORE });
70
46
  instance.set(value);
71
47
  return Object.freeze(instance);
72
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/mora",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "Signals and stuff…",
5
5
  "keywords": [
6
6
  "reactive",
@@ -39,17 +39,17 @@
39
39
  "test:leak": "vpx vp test run --detect-async-leaks --coverage"
40
40
  },
41
41
  "dependencies": {
42
- "@oscarpalmer/atoms": "^0.190"
42
+ "@oscarpalmer/atoms": "^0.202"
43
43
  },
44
44
  "devDependencies": {
45
- "@types/node": "^26.2",
46
- "@vitest/coverage-istanbul": "4.1.10",
45
+ "@types/node": "^26.5",
46
+ "@vitest/coverage-istanbul": "^4.1",
47
47
  "jsdom": "^30",
48
- "tsdown": "^0.22",
48
+ "tsdown": "^0.23",
49
49
  "typescript": "^6",
50
50
  "vite": "npm:@voidzero-dev/vite-plus-core@latest",
51
51
  "vite-plus": "latest",
52
52
  "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
53
53
  },
54
- "packageManager": "npm@11.11.1"
54
+ "packageManager": "npm@11.19.0"
55
55
  }
package/src/batch.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import {BATCH} from './constants';
2
2
  import {runEffect} from './effect';
3
- import type {EffectState, Subscription} from './models';
3
+ import {getFrozenValue, peekSimpleValue} from './helpers/value';
4
+ import type {EffectState, StoredSubscription} from './models';
5
+
6
+ // #region Functions
4
7
 
5
8
  export function flushHandlers(): void {
6
9
  if (BATCH.flushing) {
@@ -17,10 +20,15 @@ export function flushHandlers(): void {
17
20
  BATCH.handlers.clear();
18
21
 
19
22
  for (let index = 0; index < length; index += 1) {
20
- const handler = handlers[index];
21
-
22
- if (typeof (handler as Subscription).destroy === 'function') {
23
- (handler as Subscription).callback((handler as Subscription).state.value);
23
+ const [, handler] = handlers[index];
24
+ const subscription = handler as StoredSubscription;
25
+
26
+ if (typeof subscription.frozen === 'boolean') {
27
+ subscription.callback(
28
+ subscription.frozen
29
+ ? getFrozenValue(subscription.state.value)
30
+ : peekSimpleValue(subscription.state.value, subscription.copy),
31
+ );
24
32
  } else {
25
33
  runEffect(handler as EffectState);
26
34
  }
@@ -50,3 +58,5 @@ export function stopBatch(): void {
50
58
 
51
59
  flushHandlers();
52
60
  }
61
+
62
+ // #endregion
package/src/constants.ts CHANGED
@@ -1,4 +1,6 @@
1
- import type {Active, Batch, EffectState, Subscription} from './models';
1
+ import type {Active, Batch, SubscriptionType} from './models';
2
+
3
+ // #region Variables
2
4
 
3
5
  export const ACTIVE: Active = {};
4
6
 
@@ -11,7 +13,7 @@ export const ARRAY_PEEK = 10;
11
13
  export const BATCH: Batch = {
12
14
  depth: 0,
13
15
  flushing: false,
14
- handlers: new Set<EffectState | Subscription>(),
16
+ handlers: new Map(),
15
17
  };
16
18
 
17
19
  export const METHODS_AFFECTING_LENGTH = new Set<string>(['pop', 'push', 'shift', 'unshift']);
@@ -33,10 +35,46 @@ export const NAME_EFFECT = 'effect';
33
35
 
34
36
  export const NAME_MORA = '$mora';
35
37
 
38
+ export const NAME_READONLY = 'readonly';
39
+
36
40
  export const NAME_SIGNAL = 'signal';
37
41
 
38
42
  export const NAME_STORE = 'store';
39
43
 
40
- export const NAME_ALL = new Set([NAME_ARRAY, NAME_COMPUTED, NAME_SIGNAL, NAME_STORE]);
44
+ export const NAME_SUBSCRIPTION = 'subscription';
45
+
46
+ export const NAME_ALL = new Set([
47
+ NAME_ARRAY,
48
+ NAME_COMPUTED,
49
+ NAME_READONLY,
50
+ NAME_SIGNAL,
51
+ NAME_STORE,
52
+ ]);
41
53
 
42
54
  export const PROPERTY_LENGTH = 'length';
55
+
56
+ export const SUBSCRIPTION_PROPERTY = {
57
+ key: NAME_MORA,
58
+ value: NAME_SUBSCRIPTION,
59
+ };
60
+
61
+ export const SUBSCRIPTION_TYPE_COPY: SubscriptionType = 'copy';
62
+
63
+ export const SUBSCRIPTION_TYPE_FROZEN: SubscriptionType = 'frozen';
64
+
65
+ export const SUBSCRIPTION_TYPE_ORIGINAL: SubscriptionType = 'original';
66
+
67
+ export const SUBSCRIPTION_TYPE_READONLY: SubscriptionType = 'readonly';
68
+
69
+ export const SUBSCRIPTION_TYPES_COPY = new Set<SubscriptionType>([
70
+ SUBSCRIPTION_TYPE_COPY,
71
+ SUBSCRIPTION_TYPE_READONLY,
72
+ ]);
73
+
74
+ export const SUBSCRIPTION_TYPES: Set<SubscriptionType> = new Set([
75
+ ...SUBSCRIPTION_TYPES_COPY,
76
+ SUBSCRIPTION_TYPE_FROZEN,
77
+ SUBSCRIPTION_TYPE_ORIGINAL,
78
+ ]);
79
+
80
+ // #endregion
package/src/effect.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import {ACTIVE, NAME_EFFECT} from './constants';
2
+ import {ACTIVE, NAME_EFFECT, NAME_MORA} from './constants';
3
3
  import type {Effect, EffectState} from './models';
4
4
 
5
+ // #region Functions
6
+
5
7
  /**
6
8
  * Create an effect
7
9
  *
@@ -22,7 +24,7 @@ function getEffect(callback: GenericCallback): [Effect, EffectState] {
22
24
  };
23
25
 
24
26
  const instance = Object.freeze({
25
- $mora: NAME_EFFECT,
27
+ [NAME_MORA]: NAME_EFFECT,
26
28
  });
27
29
 
28
30
  runEffect(state);
@@ -45,3 +47,5 @@ export function runEffect(state: EffectState): void {
45
47
  ACTIVE.effect = previousEffect;
46
48
  }
47
49
  }
50
+
51
+ // #endregion