@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
package/src/helpers/is.ts CHANGED
@@ -5,20 +5,21 @@ import {
5
5
  NAME_COMPUTED,
6
6
  NAME_EFFECT,
7
7
  NAME_MORA,
8
+ NAME_READONLY,
8
9
  NAME_SIGNAL,
9
10
  NAME_STORE,
10
11
  } from '../constants';
11
- import type {Computed, Effect, Reactive, ReactiveArray, ReactiveStore, Signal} from '../models';
12
+ import type {
13
+ Computed,
14
+ Effect,
15
+ Reactive,
16
+ ReactiveArray,
17
+ ReactiveStore,
18
+ ReadonlySignal,
19
+ Signal,
20
+ } from '../models';
12
21
 
13
- /**
14
- * Is the value a reactive array?
15
- *
16
- * @param value Value to check
17
- * @returns True if value is a {@link ReactiveArray}
18
- */
19
- export function isArray<Item>(value: unknown): value is ReactiveArray<Item> {
20
- return isMora<ReactiveArray<Item>>(value, NAME_ARRAY);
21
- }
22
+ // #region Functions
22
23
 
23
24
  /**
24
25
  * Is the value a computed signal?
@@ -40,7 +41,7 @@ export function isEffect(value: unknown): value is Effect {
40
41
  return isMora<Effect>(value, NAME_EFFECT);
41
42
  }
42
43
 
43
- function isMora<T>(value: unknown, name: string | Set<string>): value is T {
44
+ function isMora<Instance>(value: unknown, name: string | Set<string>): value is Instance {
44
45
  return (
45
46
  typeof value === 'object' &&
46
47
  value != null &&
@@ -71,12 +72,36 @@ export function isSignal<Value>(value: unknown): value is Signal<Value> {
71
72
  return isMora<Signal<Value>>(value, NAME_SIGNAL);
72
73
  }
73
74
 
75
+ /**
76
+ * Is the value a reactive array?
77
+ *
78
+ * @param value Value to check
79
+ * @returns True if value is a {@link ReactiveArray}
80
+ */
81
+ export function isReactiveArray<Item>(value: unknown): value is ReactiveArray<Item> {
82
+ return isMora<ReactiveArray<Item>>(value, NAME_ARRAY);
83
+ }
84
+
74
85
  /**
75
86
  * Is the value a reactive store?
76
87
  *
77
88
  * @param value Value to check
78
89
  * @returns True if value is a {@link Store}
79
90
  */
80
- export function isStore<Value extends PlainObject>(value: unknown): value is ReactiveStore<Value> {
91
+ export function isReactiveStore<Value extends PlainObject>(
92
+ value: unknown,
93
+ ): value is ReactiveStore<Value> {
81
94
  return isMora<ReactiveStore<Value>>(value, NAME_STORE);
82
95
  }
96
+
97
+ /**
98
+ * Is the value a readonly signal?
99
+ *
100
+ * @param value Value to check
101
+ * @returns True if value is a {@link ReadonlySignal}
102
+ */
103
+ export function isReadonlySignal<Value>(value: unknown): value is ReadonlySignal<Value> {
104
+ return isMora<ReadonlySignal<Value>>(value, NAME_READONLY);
105
+ }
106
+
107
+ // #endregion
@@ -1,3 +1,4 @@
1
+ import {isKey, isPlainObject} from '@oscarpalmer/atoms/is';
1
2
  import type {ArrayOrPlainObject, Key, PlainObject} from '@oscarpalmer/atoms/models';
2
3
  import {PROPERTY_LENGTH} from '../constants';
3
4
  import type {
@@ -6,23 +7,30 @@ import type {
6
7
  ReactiveArray,
7
8
  ReactiveState,
8
9
  ReactiveStore,
9
- SetValueInProxyParameters,
10
+ Signal,
10
11
  } from '../models';
11
12
  import {internalComputed} from '../value/computed';
12
- import {emitValue} from './value';
13
+ import {emitValue, getSimpleValue, peekSimpleValue} from './value';
13
14
 
14
- export function emityProxyValues<Value>(
15
- state: ReactiveState<Value, Value>,
16
- mapped: Map<Key, [Computed<unknown>, ComputedEffect]>,
17
- ): void;
15
+ // #region Types
18
16
 
19
- export function emityProxyValues<Value>(
20
- state: ReactiveState<Value[], Value>,
21
- mapped: Map<Key, [Computed<unknown>, ComputedEffect]>,
22
- ): void;
17
+ type SetObjectCallback<Value, Item> = (
18
+ state: ReactiveState<Value, Item>,
19
+ value: Value | undefined,
20
+ ) => void;
21
+
22
+ type SetPropertyCallback<Value, Item> = (
23
+ state: ReactiveState<Value, Item>,
24
+ property: unknown,
25
+ value: Item,
26
+ ) => void;
27
+
28
+ // #endregion
23
29
 
24
- export function emityProxyValues(
25
- state: ReactiveState<unknown, unknown>,
30
+ // #region Functions
31
+
32
+ export function emitProxyValues<Value, Item = Value>(
33
+ state: ReactiveState<Value, Item>,
26
34
  mapped: Map<Key, [Computed<unknown>, ComputedEffect]>,
27
35
  ): void {
28
36
  const values = [...mapped.values()];
@@ -35,27 +43,15 @@ export function emityProxyValues(
35
43
  emitValue(state);
36
44
  }
37
45
 
38
- export function getReactiveValueInProxy<Value, Result = Value>(
39
- array: ReactiveArray<Value>,
40
- mapped: Map<Key, [Computed<unknown>, ComputedEffect]>,
41
- index: number,
42
- isArray: true,
43
- ): Computed<Result>;
44
-
45
- export function getReactiveValueInProxy<Value extends PlainObject>(
46
- store: ReactiveStore<Value>,
47
- mapped: Map<Key, [Computed<unknown>, ComputedEffect]>,
48
- key: Key,
49
- isArray: false,
50
- ): Computed<unknown>;
51
-
52
- export function getReactiveValueInProxy(
53
- reactive: ReactiveArray<unknown> | ReactiveStore<PlainObject>,
46
+ export function getReactiveValueInProxy<Value>(
47
+ reactive: ReactiveArray<Value> | ReactiveStore<Value>,
54
48
  mapped: Map<Key, [Computed<unknown>, ComputedEffect]>,
55
49
  key: Key,
56
50
  isArray: boolean,
57
51
  ): Computed<unknown> {
58
- let item = mapped.get(key);
52
+ const mapKey = String(key);
53
+
54
+ let item = mapped.get(mapKey);
59
55
 
60
56
  if (item == null) {
61
57
  item = internalComputed(() =>
@@ -64,37 +60,72 @@ export function getReactiveValueInProxy(
64
60
  : (reactive.get() as PlainObject)[key],
65
61
  );
66
62
 
67
- mapped.set(key, item);
63
+ mapped.set(mapKey, item);
68
64
  }
69
65
 
70
66
  return item[0];
71
67
  }
72
68
 
69
+ export function getValueInProxy<Value, Item = Value>(
70
+ isArray: boolean,
71
+ instance: ReactiveArray<Value> | ReactiveStore<Value>,
72
+ state: ReactiveState<Value, Item>,
73
+ mapped: Map<Key, [Computed<unknown>, ComputedEffect]>,
74
+ first?: unknown,
75
+ ): unknown {
76
+ if (isArray ? typeof first === 'number' : isKey(first)) {
77
+ return getReactiveValueInProxy(instance, mapped, first as Key, isArray).get();
78
+ }
79
+
80
+ return getSimpleValue(state);
81
+ }
82
+
83
+ function isProxyObject(isArray: boolean, value: unknown): value is ArrayOrPlainObject {
84
+ return value == null || (isArray ? Array.isArray(value) : isPlainObject(value));
85
+ }
86
+
87
+ export function peekValueInProxy<Value, Item = Value>(
88
+ isArray: boolean,
89
+ state: ReactiveState<Value, Item>,
90
+ first?: unknown,
91
+ second?: boolean,
92
+ ): unknown {
93
+ let value: unknown;
94
+
95
+ if (isArray ? typeof first === 'number' : isKey(first)) {
96
+ value = isArray
97
+ ? (state.value as unknown[]).at(first as number)
98
+ : (state.value as PlainObject)[first as Key];
99
+ } else {
100
+ value = state.value;
101
+ }
102
+
103
+ return peekSimpleValue(value, first === true || second === true);
104
+ }
105
+
73
106
  export function setProxyValue<Value, Item = Value>(
74
- array: boolean,
107
+ isArray: boolean,
75
108
  state: ReactiveState<Value, Item>,
76
- isObject: (value: unknown) => value is Value | undefined,
77
- isProperty: (value: unknown) => boolean,
78
- setObject: (state: ReactiveState<Value, Item>, value: Value | undefined) => void,
79
- setProperty: (state: ReactiveState<Value, Item>, property: unknown, value: Item) => void,
109
+ setObject: SetObjectCallback<Value, Item>,
110
+ setProperty: SetPropertyCallback<Value, Item>,
80
111
  first?: unknown,
81
112
  second?: unknown,
82
113
  ): void {
83
- if (array && first === PROPERTY_LENGTH) {
114
+ if (isArray && first === PROPERTY_LENGTH) {
84
115
  (state.value as unknown[]).length = second as number;
85
116
 
86
117
  return;
87
118
  }
88
119
 
89
- if (isObject(first)) {
90
- setObject(state, first);
120
+ if (isProxyObject(isArray, first)) {
121
+ setObject(state, first as Value);
91
122
 
92
123
  return;
93
124
  }
94
125
 
95
- const property = isProperty(first);
126
+ const property = isArray ? typeof first === 'number' : isKey(first);
96
127
 
97
- if (array && property && Number.isNaN(first)) {
128
+ if (isArray && property && Number.isNaN(first)) {
98
129
  return;
99
130
  }
100
131
 
@@ -127,10 +158,10 @@ export function setProxyValue<Value, Item = Value>(
127
158
  return;
128
159
  }
129
160
 
130
- if (!property && isObject(value) && state.promise === actual) {
161
+ if (!property && isProxyObject(isArray, value) && state.promise === actual) {
131
162
  state.promise = undefined;
132
163
 
133
- setObject(state, value);
164
+ setObject(state, value as Value);
134
165
  }
135
166
  })
136
167
  .catch(() => {
@@ -142,16 +173,19 @@ export function setProxyValue<Value, Item = Value>(
142
173
  });
143
174
  } else if (property) {
144
175
  setProperty(state, first, actual as Item);
145
- } else if (isObject(actual)) {
146
- setObject(state, actual);
176
+ } else if (isProxyObject(isArray, actual)) {
177
+ setObject(state, actual as Value);
147
178
  }
148
179
  }
149
180
 
150
- export function setValueInProxy<Value extends ArrayOrPlainObject, Equal>(
151
- parameters: SetValueInProxyParameters<Value, Equal>,
181
+ export function setValueInProxy<Value, Item = Value>(
182
+ isArray: boolean,
183
+ state: ReactiveState<Value, Item>,
184
+ target: object,
185
+ property: PropertyKey,
186
+ value: unknown,
187
+ length?: Signal<number>,
152
188
  ): boolean {
153
- const {isArray, length, property, state, target, value} = parameters;
154
-
155
189
  if (isArray) {
156
190
  const isIndex = !Number.isNaN(Number(property));
157
191
  const isLength = property === PROPERTY_LENGTH;
@@ -161,7 +195,7 @@ export function setValueInProxy<Value extends ArrayOrPlainObject, Equal>(
161
195
  }
162
196
  }
163
197
 
164
- const previous = Reflect.get(target, property);
198
+ const previous = Reflect.get(target as object, property);
165
199
 
166
200
  if (!state.equal(previous as never, value as never)) {
167
201
  Reflect.set(target, property, value);
@@ -175,3 +209,19 @@ export function setValueInProxy<Value extends ArrayOrPlainObject, Equal>(
175
209
 
176
210
  return true;
177
211
  }
212
+
213
+ export function updateProxyValue<Value, Item = Value>(
214
+ isArray: boolean,
215
+ state: ReactiveState<Value, Item>,
216
+ setObject: SetObjectCallback<Value, Item>,
217
+ setProperty: SetPropertyCallback<Value, Item>,
218
+ callback: unknown,
219
+ ): void {
220
+ if (typeof callback !== 'function') {
221
+ throw new TypeError('Callback must be a function');
222
+ }
223
+
224
+ setProxyValue(isArray, state, setObject, setProperty, callback(state.value));
225
+ }
226
+
227
+ // #endregion
@@ -0,0 +1,78 @@
1
+ import {isKey} from '@oscarpalmer/atoms/is';
2
+ import type {Key} from '@oscarpalmer/atoms/models';
3
+ import {subscriptions, type Subscription} from '@oscarpalmer/atoms/subscription';
4
+ import {
5
+ SUBSCRIPTION_PROPERTY,
6
+ SUBSCRIPTION_TYPE_COPY,
7
+ SUBSCRIPTION_TYPE_FROZEN,
8
+ SUBSCRIPTION_TYPE_ORIGINAL,
9
+ SUBSCRIPTION_TYPES,
10
+ SUBSCRIPTION_TYPES_COPY,
11
+ } from '../constants';
12
+ import type {
13
+ Computed,
14
+ ComputedEffect,
15
+ ReactiveArray,
16
+ ReactiveState,
17
+ ReactiveStore,
18
+ SubscriptionType,
19
+ } from '../models';
20
+ import {getReactiveValueInProxy} from './proxy';
21
+ import {getFrozenValue, peekSimpleValue} from './value';
22
+
23
+ export function subscribeToProxy<Value, Item = Value>(
24
+ isArray: boolean,
25
+ instance: ReactiveArray<Value> | ReactiveStore<Value>,
26
+ state: ReactiveState<Value, Item>,
27
+ mapped: Map<Key, [Computed<unknown>, ComputedEffect]>,
28
+ first: Key | ((value: unknown) => unknown),
29
+ second?: ((value: unknown) => unknown) | boolean,
30
+ third?: boolean,
31
+ ): Subscription {
32
+ if (isKey(first)) {
33
+ return getReactiveValueInProxy(instance as never, mapped, first, isArray).subscribe(
34
+ second as never,
35
+ third as never,
36
+ );
37
+ }
38
+
39
+ return subscribeToSignal(state, first, second === true);
40
+ }
41
+
42
+ export function subscribeToReactive<Value, Item = Value>(
43
+ type: SubscriptionType,
44
+ state: ReactiveState<Value, Item>,
45
+ callback: (value: unknown) => unknown,
46
+ ): Subscription {
47
+ state.subscriptions ??= subscriptions({
48
+ keys: SUBSCRIPTION_TYPES,
49
+ property: SUBSCRIPTION_PROPERTY,
50
+ });
51
+
52
+ const [subscription, existing] = state.subscriptions.create({
53
+ key: type,
54
+ value: callback,
55
+ });
56
+
57
+ if (!existing) {
58
+ callback(
59
+ type === SUBSCRIPTION_TYPE_FROZEN
60
+ ? getFrozenValue(state.value)
61
+ : peekSimpleValue(state.value, SUBSCRIPTION_TYPES_COPY.has(type)),
62
+ );
63
+ }
64
+
65
+ return subscription;
66
+ }
67
+
68
+ export function subscribeToSignal<Value, Item = Value>(
69
+ state: ReactiveState<Value, Item>,
70
+ callback: (value: unknown) => unknown,
71
+ copy: boolean,
72
+ ): Subscription {
73
+ return subscribeToReactive(
74
+ copy ? SUBSCRIPTION_TYPE_COPY : SUBSCRIPTION_TYPE_ORIGINAL,
75
+ state,
76
+ callback,
77
+ );
78
+ }
@@ -1,18 +1,41 @@
1
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
1
2
  import {flushHandlers} from '../batch';
2
- import {ACTIVE, ARRAY_OFFSET, ARRAY_PEEK, ARRAY_THRESHOLD, BATCH} from '../constants';
3
+ import {
4
+ ACTIVE,
5
+ ARRAY_OFFSET,
6
+ ARRAY_PEEK,
7
+ ARRAY_THRESHOLD,
8
+ BATCH,
9
+ SUBSCRIPTION_TYPES_COPY,
10
+ SUBSCRIPTION_TYPE_FROZEN,
11
+ SUBSCRIPTION_TYPES,
12
+ } from '../constants';
3
13
  import type {ReactiveState} from '../models';
4
14
 
15
+ // #region Functions
16
+
5
17
  export function emitValue<Value>(state: ReactiveState<Value, never>): void {
6
18
  for (const computed of state.computeds) {
7
19
  computed.dirty = true;
8
20
  }
9
21
 
10
22
  for (const effect of state.effects) {
11
- BATCH.handlers.add(effect);
23
+ BATCH.handlers.set(effect, effect);
12
24
  }
13
25
 
14
- for (const [, subscription] of state.subscriptions) {
15
- BATCH.handlers.add(subscription);
26
+ for (const type of SUBSCRIPTION_TYPES) {
27
+ const subscriptions = state.subscriptions?.values.to.keyed?.get(type);
28
+
29
+ if (subscriptions != null) {
30
+ for (const [subscription, callback] of subscriptions) {
31
+ BATCH.handlers.set(subscription, {
32
+ callback,
33
+ state,
34
+ frozen: type === SUBSCRIPTION_TYPE_FROZEN,
35
+ copy: SUBSCRIPTION_TYPES_COPY.has(type),
36
+ });
37
+ }
38
+ }
16
39
  }
17
40
 
18
41
  if (BATCH.depth === 0) {
@@ -55,6 +78,18 @@ export function equalArrays<Value>(
55
78
  return true;
56
79
  }
57
80
 
81
+ export function getFrozenValue(value: unknown): unknown {
82
+ let frozen = value;
83
+
84
+ if (Array.isArray(frozen)) {
85
+ frozen = Object.freeze(frozen.slice());
86
+ } else if (isPlainObject(frozen)) {
87
+ frozen = Object.freeze({...frozen});
88
+ }
89
+
90
+ return frozen;
91
+ }
92
+
58
93
  export function getSimpleValue<Value>(state: ReactiveState<Value, never>): Value {
59
94
  if (ACTIVE.computed != null) {
60
95
  state.computeds.add(ACTIVE.computed);
@@ -101,3 +136,33 @@ export function handleSimpleValue<Value>(
101
136
  onAfter?.();
102
137
  }
103
138
  }
139
+
140
+ export function peekSimpleValue(value: unknown, copy: boolean): unknown {
141
+ let peeked: unknown = value;
142
+
143
+ if (!copy) {
144
+ return peeked;
145
+ }
146
+
147
+ if (Array.isArray(peeked)) {
148
+ peeked = peeked.slice();
149
+ } else if (isPlainObject(peeked)) {
150
+ peeked = {...peeked};
151
+ }
152
+
153
+ return peeked;
154
+ }
155
+
156
+ export function updateSimpleValue<Value>(
157
+ state: ReactiveState<Value, Value>,
158
+ callback: (value: Value) => Value,
159
+ setValue: (state: ReactiveState<Value, Value>, value: Value) => void,
160
+ ): void {
161
+ if (typeof callback !== 'function') {
162
+ throw new TypeError('Callback must be a function');
163
+ }
164
+
165
+ handleSimpleValue(state, callback(state.value), setValue);
166
+ }
167
+
168
+ // #endregion
package/src/index.ts CHANGED
@@ -1,7 +1,23 @@
1
+ export type {Subscription} from '@oscarpalmer/atoms/subscription';
1
2
  export {startBatch, stopBatch} from './batch';
2
3
  export {effect} from './effect';
3
- export {isArray, isComputed, isEffect, isReactive, isSignal} from './helpers/is';
4
- export type {Computed, Effect, ReactiveArray, ReactiveStore, Signal, Unsubscribe} from './models';
4
+ export {
5
+ isComputed,
6
+ isEffect,
7
+ isReactive,
8
+ isReactiveArray,
9
+ isReactiveStore,
10
+ isReadonlySignal,
11
+ isSignal,
12
+ } from './helpers/is';
13
+ export type {
14
+ Computed,
15
+ Effect,
16
+ ReactiveArray,
17
+ ReactiveStore,
18
+ ReadonlySignal,
19
+ Signal,
20
+ } from './models';
5
21
  export {array} from './value/array';
6
22
  export {computed} from './value/computed';
7
23
  export {signal} from './value/signal';