@oscarpalmer/mora 0.28.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 (42) 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 -8
  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 +21 -3
  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 +458 -409
  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 -38
  23. package/dist/value/reactive.mjs +10 -49
  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 +8 -8
  29. package/src/batch.ts +22 -9
  30. package/src/constants.ts +3 -4
  31. package/src/effect.ts +31 -26
  32. package/src/helpers/is.ts +11 -10
  33. package/src/helpers/proxy.ts +23 -18
  34. package/src/helpers/value.ts +38 -3
  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 -64
  41. package/src/value/signal.ts +34 -71
  42. package/src/value/store.ts +84 -177
@@ -1,72 +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
- 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> = {
7
8
  computeds: new Set(),
8
9
  effects: new Set(),
9
- equal: Object.is,
10
+ equal:
11
+ typeof options === 'object' && typeof options?.equal === 'function'
12
+ ? options.equal
13
+ : Object.is,
10
14
  subscriptions: new Map(),
11
- value: undefined as never,
15
+ value: value as Value,
12
16
  };
13
17
 
14
- constructor(name: string, value: Value, options?: ReactiveOptions<Equal>) {
15
- this.state.value = value;
16
-
17
- if (typeof options === 'object' && typeof options.equal === 'function') {
18
- this.state.equal = options.equal;
19
- }
20
-
21
- Object.defineProperty(this, NAME_MORA, {
22
- value: name,
23
- });
24
- }
25
-
26
- /**
27
- * Get the value
28
- * @return Current value
29
- */
30
- abstract get(): Value;
31
-
32
- /**
33
- * Get the value _(without reactivity)_
34
- * @return Current value
35
- */
36
- peek(): Value {
37
- return this.state.value;
38
- }
39
-
40
- /**
41
- * Subscribe to changes
42
- * @param callback Callback for changes
43
- * @return Unsubscribe callback
44
- */
45
- subscribe(callback: (value: Value) => void): Unsubscribe {
46
- return subscribe(this.state, callback);
47
- }
48
-
49
- /**
50
- * JSON representation of the value
51
- * @return JSON value
52
- */
53
- toJSON(): Value {
54
- return this.get();
55
- }
56
-
57
- /**
58
- * String representation of the value
59
- * @return Value as string
60
- */
61
- toString(): string {
62
- return String(this.get());
63
- }
18
+ const instance = {
19
+ toJSON: () => state.value,
20
+ toString: () => String(state.value),
21
+ };
64
22
 
65
- /**
66
- * Unsubscribe from changes
67
- * @param callback Callback to unsubscribe
68
- */
69
- unsubscribe(callback: (value: Value) => void): void {
70
- unsubscribe(this.state, callback);
71
- }
23
+ return [instance, state];
72
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
  }
@@ -1,183 +1,24 @@
1
1
  import {isKey, isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import type {GenericCallback, Key, PlainObject} from '@oscarpalmer/atoms/models';
3
3
  import {startBatch, stopBatch} from '../batch';
4
- import {NAME_STORE} from '../constants';
4
+ import {NAME_MORA, NAME_STORE} from '../constants';
5
5
  import {
6
6
  emityProxyValues,
7
7
  getReactiveValueInProxy,
8
8
  setProxyValue,
9
9
  setValueInProxy,
10
10
  } from '../helpers/proxy';
11
- import {getValue} from '../helpers/value';
12
- import type {ReactiveOptions, ReactiveState, Unsubscribe} from '../models';
13
- import {noop, subscribe} from '../subscription';
14
- import type {Computed} from './computed';
15
- import {Reactive} from './reactive';
16
-
17
- export class Store<Value extends PlainObject> extends Reactive<Value, Value> {
18
- readonly #keyed = new Map<Key, Computed<unknown>>();
19
-
20
- constructor(value: Value, options?: ReactiveOptions<Value>) {
21
- super(
22
- NAME_STORE,
23
- new Proxy(value, {
24
- set: (target: Value, property: PropertyKey, value: unknown) =>
25
- setValueInProxy({
26
- target,
27
- property,
28
- value,
29
- isArray: false,
30
- state: this.state,
31
- }),
32
- }),
33
- options,
34
- );
35
- }
36
-
37
- /**
38
- * @inheritdoc
39
- */
40
- get(): Value;
41
-
42
- /**
43
- * Get a value by key
44
- * @param key Key of the value to get
45
- * @returns Value for the specified key, or `undefined` if it doesn't exist
46
- */
47
- get<Key extends keyof Value>(key: Key): Value[Key];
48
-
49
- /**
50
- * Get a value by key
51
- * @param key Key of the value to get
52
- * @returns Value for the specified key, or `undefined` if it doesn't exist
53
- */
54
- get(key: Key): unknown;
55
-
56
- get(key?: unknown): unknown {
57
- return isKey(key)
58
- ? getReactiveValueInProxy(this, this.#keyed, key, false).get()
59
- : getValue(this.state);
60
- }
61
-
62
- /**
63
- * Notify dependents of changes
64
- *
65
- * _This bypasses equality checks and will immediately notify dependents.
66
- * Use this only if you're modifying nested data that would be ignored by equality checks._
67
- */
68
- notify(): void {
69
- emityProxyValues(this.state, this.#keyed);
70
- }
71
-
72
- /**
73
- * Get the value _(without reactivity)_
74
- * @returns The current value
75
- */
76
- override peek(): Value;
77
-
78
- /**
79
- * Get a value by key _(without reactivity)_
80
- * @param key Key of the value to get
81
- * @returns Value for the specified key, or `undefined` if it doesn't exist
82
- */
83
- override peek<Key extends keyof Value>(key: Key): Value[Key];
84
-
85
- /**
86
- * Get a value by key _(without reactivity)_
87
- * @param key Key of the value to get
88
- * @returns Value for the specified key, or `undefined` if it doesn't exist
89
- */
90
- override peek(key: Key): unknown;
91
-
92
- override peek(key?: unknown): unknown {
93
- return isKey(key) ? this.state.value[key] : {...this.state.value};
94
- }
95
-
96
- /**
97
- * Set the value
98
- * @param value New value _(defaults to an empty object)_
99
- */
100
- set(
101
- value?:
102
- | Value
103
- | (() => null | undefined | Value | Promise<null | undefined | Value>)
104
- | Promise<null | undefined | Value>,
105
- ): void;
106
-
107
- /**
108
- * Set a value by key
109
- * @param key Key of the value to set
110
- * @param value New value
111
- */
112
- set<Key extends keyof Value>(
113
- key: Key,
114
- value: Value[Key] | (() => Value[Key] | Promise<Value[Key]>) | Promise<Value[Key]>,
115
- ): void;
116
-
117
- /**
118
- * Set a value by key
119
- * @param key Key of the value to set
120
- * @param value New value
121
- */
122
- set(key: Key, value: unknown): void;
123
-
124
- set(first?: unknown, second?: unknown): void {
125
- setProxyValue<Value>(
126
- false,
127
- this.state,
128
- isStoreObject,
129
- isKey,
130
- setObject,
131
- setProperty,
132
- first,
133
- second,
134
- );
135
- }
136
-
137
- /**
138
- * @inheritdoc
139
- */
140
- override subscribe(callback: (value: Value) => void): Unsubscribe;
141
-
142
- /**
143
- * Subscribe to changes for a specific key
144
- * @param key Key of the value to subscribe to
145
- * @param callback Callback for changes
146
- * @returns Unsubscribe callback
147
- */
148
- override subscribe<Key extends keyof Value>(
149
- key: Key,
150
- callback: (value: Value[Key] | undefined) => void,
151
- ): Unsubscribe;
152
-
153
- /**
154
- * Subscribe to changes for a specific key
155
- * @param key Key of the value to subscribe to
156
- * @param callback Callback for changes
157
- * @returns Unsubscribe callback
158
- */
159
- override subscribe(key: Key, callback: (value: unknown) => void): Unsubscribe;
160
-
161
- override subscribe(first: Key | GenericCallback, second?: GenericCallback): Unsubscribe {
162
- if (isKey(first) && typeof second === 'function') {
163
- return getReactiveValueInProxy(this, this.#keyed, first, false).subscribe(second);
164
- }
165
-
166
- return typeof first === 'function' ? subscribe(this.state, first) : noop;
167
- }
168
-
169
- /**
170
- * Update the value _(based on the current value)_
171
- * @param callback Callback to update the value
172
- */
173
- update(callback: (value: Value) => Value): void {
174
- const updated = callback(this.state.value);
175
-
176
- if (updated == null || isPlainObject(updated)) {
177
- setObject(this.state, updated);
178
- }
179
- }
180
- }
11
+ import {getSimpleValue} from '../helpers/value';
12
+ import type {
13
+ Computed,
14
+ ComputedEffect,
15
+ ReactiveOptions,
16
+ ReactiveState,
17
+ ReactiveStore,
18
+ Unsubscribe,
19
+ } from '../models';
20
+ import {noop, subscribe, unsubscribe} from '../subscription';
21
+ import {reactive} from './reactive';
181
22
 
182
23
  function isStoreObject<Value extends PlainObject>(value: unknown): value is Value | undefined {
183
24
  return value == null || isPlainObject(value);
@@ -225,6 +66,7 @@ function setProperty<Value extends PlainObject>(
225
66
 
226
67
  /**
227
68
  * Create a reactive store from a function result
69
+ *
228
70
  * @param value Initial object value
229
71
  * @param options Reactivity options
230
72
  * @returns Reactive store
@@ -232,10 +74,11 @@ function setProperty<Value extends PlainObject>(
232
74
  export function store<Value extends PlainObject>(
233
75
  value: () => Value | Promise<Value>,
234
76
  options?: ReactiveOptions<Value>,
235
- ): Store<Value>;
77
+ ): ReactiveStore<Value>;
236
78
 
237
79
  /**
238
80
  * Create a reactive store from a promise
81
+ *
239
82
  * @param value Initial object value
240
83
  * @param options Reactivity options
241
84
  * @returns Reactive store
@@ -243,10 +86,11 @@ export function store<Value extends PlainObject>(
243
86
  export function store<Value extends PlainObject>(
244
87
  value: Promise<Value>,
245
88
  options?: ReactiveOptions<Value>,
246
- ): Store<Value>;
89
+ ): ReactiveStore<Value>;
247
90
 
248
91
  /**
249
92
  * Create a reactive store
93
+ *
250
94
  * @param value Initial object value
251
95
  * @param options Reactivity options
252
96
  * @returns Reactive store
@@ -254,15 +98,78 @@ export function store<Value extends PlainObject>(
254
98
  export function store<Value extends PlainObject>(
255
99
  value: Value,
256
100
  options?: ReactiveOptions<Value>,
257
- ): Store<Value>;
101
+ ): ReactiveStore<Value>;
258
102
 
259
103
  export function store<Value extends PlainObject>(
260
104
  value: Value | (() => Value | Promise<Value>) | Promise<Value>,
261
105
  options?: ReactiveOptions<Value>,
262
- ): Store<Value> {
263
- const instance = new Store({} as unknown as Value, options);
106
+ ): ReactiveStore<Value> {
107
+ const [rx, state] = reactive<Value>(undefined as never, options);
108
+
109
+ const keyed = new Map<Key, [Computed<unknown>, ComputedEffect]>();
110
+
111
+ state.value = new Proxy({} as Value, {
112
+ set: (target: Value, property: PropertyKey, value: unknown) =>
113
+ setValueInProxy({
114
+ target,
115
+ property,
116
+ state,
117
+ value,
118
+ isArray: false,
119
+ }),
120
+ });
121
+
122
+ const instance = {
123
+ ...rx,
124
+ get: (value?: unknown): unknown =>
125
+ isKey(value)
126
+ ? getReactiveValueInProxy(instance, keyed, value, false).get()
127
+ : getSimpleValue(state),
128
+ notify(): void {
129
+ emityProxyValues(state, keyed);
130
+ },
131
+ peek: (value?: unknown): unknown => (isKey(value) ? state.value[value] : {...state.value}),
132
+ set: (first?: unknown, second?: unknown) => {
133
+ setProxyValue<Value>(
134
+ false,
135
+ state,
136
+ isStoreObject,
137
+ isKey,
138
+ setObject,
139
+ setProperty,
140
+ first,
141
+ second,
142
+ );
143
+ },
144
+ subscribe: (first: Key | GenericCallback, second?: GenericCallback): Unsubscribe => {
145
+ if (isKey(first) && typeof second === 'function') {
146
+ return getReactiveValueInProxy(instance, keyed, first, false).subscribe(second);
147
+ }
148
+
149
+ return typeof first === 'function' ? subscribe(state, first) : noop;
150
+ },
151
+ unsubscribe: (first: Key | GenericCallback, second?: GenericCallback) => {
152
+ if (isKey(first) && typeof second === 'function') {
153
+ getReactiveValueInProxy(instance, keyed, first, false)?.unsubscribe(second);
154
+ } else if (typeof first === 'function') {
155
+ unsubscribe(state, first);
156
+ }
157
+ },
158
+ update: (callback: (value: Value) => Value) => {
159
+ const updated = callback(state.value);
160
+
161
+ if (updated == null || isPlainObject(updated)) {
162
+ setObject(state, updated);
163
+ }
164
+ },
165
+ };
166
+
167
+ Object.defineProperty(instance, NAME_MORA, {
168
+ enumerable: false,
169
+ value: NAME_STORE,
170
+ });
264
171
 
265
172
  instance.set(value);
266
173
 
267
- return instance;
174
+ return Object.freeze(instance) as ReactiveStore<Value>;
268
175
  }