@oscarpalmer/mora 0.27.0 → 0.28.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.
@@ -1,6 +1,7 @@
1
+ import {flushHandlers} from '../batch';
1
2
  import {ACTIVE, BATCH, NAME_COMPUTED} from '../constants';
2
3
  import {effect, runEffect} from '../effect';
3
- import type {ComputedEffect, ReactiveOptions} from '../models';
4
+ import type {ComputedEffect, InternalComputed, ReactiveOptions, ReactiveState} from '../models';
4
5
  import {Reactive} from './reactive';
5
6
 
6
7
  export class Computed<Value> extends Reactive<Value> {
@@ -9,39 +10,15 @@ export class Computed<Value> extends Reactive<Value> {
9
10
  instance: undefined as never,
10
11
  };
11
12
 
12
- constructor(callback: () => Value, options?: ReactiveOptions<Value>) {
13
+ constructor(callback: () => Value | Promise<Value>, options?: ReactiveOptions<Value>) {
13
14
  super(NAME_COMPUTED, undefined as never, options);
14
15
 
15
16
  this.effect.instance = effect(() => {
16
- if (!this.effect.dirty) {
17
- return;
18
- }
19
-
20
- const previousComputed = ACTIVE.computed;
21
-
22
- ACTIVE.computed = this as never;
23
-
24
- const value = callback();
25
-
26
- ACTIVE.computed = previousComputed;
27
-
28
- if (!this.state.equal(this.state.value, value)) {
29
- this.state.value = value;
17
+ if (this.effect.dirty) {
18
+ setValue(this, this.state, callback);
30
19
 
31
- for (const computed of this.state.computeds) {
32
- computed.effect.dirty = true;
33
- }
34
-
35
- for (const effect of this.state.effects) {
36
- BATCH.handlers.add(effect);
37
- }
38
-
39
- for (const [, subscription] of this.state.subscriptions) {
40
- subscription.callback(value);
41
- }
20
+ this.effect.dirty = false;
42
21
  }
43
-
44
- this.effect.dirty = false;
45
22
  });
46
23
  }
47
24
 
@@ -72,8 +49,66 @@ export class Computed<Value> extends Reactive<Value> {
72
49
  * @returns Computed value
73
50
  */
74
51
  export function computed<Value>(
75
- callback: () => Value,
52
+ callback: () => Value | Promise<Value>,
76
53
  options?: ReactiveOptions<Value>,
77
54
  ): Computed<Value> {
78
55
  return new Computed(callback, options);
79
56
  }
57
+
58
+ function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): void {
59
+ if (state.equal(state.value, value)) {
60
+ return;
61
+ }
62
+
63
+ state.value = value;
64
+
65
+ for (const computed of state.computeds) {
66
+ (computed as unknown as InternalComputed).effect.dirty = true;
67
+ }
68
+
69
+ for (const effect of state.effects) {
70
+ BATCH.handlers.add(effect);
71
+ }
72
+
73
+ for (const [, subscription] of state.subscriptions) {
74
+ subscription.callback(value);
75
+ }
76
+
77
+ flushHandlers();
78
+ }
79
+
80
+ function setValue<Value>(
81
+ instance: Computed<Value>,
82
+ state: ReactiveState<Value, Value>,
83
+ callback: () => Value | Promise<Value>,
84
+ ): void {
85
+ const previousComputed = ACTIVE.computed;
86
+
87
+ ACTIVE.computed = instance as never;
88
+
89
+ try {
90
+ const value = callback();
91
+
92
+ if (value instanceof Promise) {
93
+ state.promise = value;
94
+
95
+ value
96
+ .then(resolvedValue => {
97
+ if (state.promise === value) {
98
+ state.promise = undefined;
99
+
100
+ setAndEmit(state, resolvedValue);
101
+ }
102
+ })
103
+ .catch(() => {
104
+ if (state.promise === value) {
105
+ state.promise = undefined;
106
+ }
107
+ });
108
+ } else {
109
+ setAndEmit(state, value);
110
+ }
111
+ } finally {
112
+ ACTIVE.computed = previousComputed;
113
+ }
114
+ }
@@ -1,6 +1,6 @@
1
1
  import {NAME_SIGNAL} from '../constants';
2
2
  import {emitValue, getValue} from '../helpers/value';
3
- import type {ReactiveOptions} from '../models';
3
+ import type {ReactiveOptions, ReactiveState} from '../models';
4
4
  import {Reactive} from './reactive';
5
5
 
6
6
  export class Signal<Value> extends Reactive<Value> {
@@ -19,12 +19,8 @@ export class Signal<Value> extends Reactive<Value> {
19
19
  * Set the value
20
20
  * @param value New value
21
21
  */
22
- set(value: Value): void {
23
- if (!this.state.equal(this.state.value, value)) {
24
- this.state.value = value;
25
-
26
- emitValue(this.state);
27
- }
22
+ set(value: Value | (() => Value | Promise<Value>) | Promise<Value>): void {
23
+ setValue<Value>(this.state, value);
28
24
  }
29
25
 
30
26
  /**
@@ -36,12 +32,84 @@ export class Signal<Value> extends Reactive<Value> {
36
32
  }
37
33
  }
38
34
 
35
+ function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): void {
36
+ if (!state.equal(state.value, value)) {
37
+ state.value = value;
38
+
39
+ emitValue(state);
40
+ }
41
+ }
42
+
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
+ /**
77
+ * Create a reactive value from a function result
78
+ * @param value Initial value
79
+ * @param options Reactivity options
80
+ * @returns Reactive value
81
+ */
82
+ export function signal<Value>(
83
+ value: () => Value | Promise<Value>,
84
+ options?: ReactiveOptions<Value>,
85
+ ): Signal<Value>;
86
+
87
+ /**
88
+ * Create a reactive value from a promise
89
+ * @param value Initial value
90
+ * @param options Reactivity options
91
+ * @returns Reactive value
92
+ */
93
+ export function signal<Value>(
94
+ value: Promise<Value>,
95
+ options?: ReactiveOptions<Value>,
96
+ ): Signal<Value>;
97
+
39
98
  /**
40
99
  * Create a reactive value
41
100
  * @param value Initial value
42
101
  * @param options Reactivity options
43
102
  * @returns Reactive value
44
103
  */
45
- export function signal<Value>(value: Value, options?: ReactiveOptions<Value>): Signal<Value> {
46
- return new Signal<Value>(value, options);
104
+ export function signal<Value>(value: Value, options?: ReactiveOptions<Value>): Signal<Value>;
105
+
106
+ export function signal<Value>(
107
+ value: Value | (() => Value | Promise<Value>) | Promise<Value>,
108
+ options?: ReactiveOptions<Value>,
109
+ ): Signal<Value> {
110
+ const instance = new Signal<Value>(undefined as unknown as Value, options);
111
+
112
+ instance.set(value);
113
+
114
+ return instance;
47
115
  }
@@ -1,5 +1,6 @@
1
1
  import {isKey, isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import type {GenericCallback, Key, PlainObject} from '@oscarpalmer/atoms/models';
3
+ import {startBatch, stopBatch} from '../batch';
3
4
  import {NAME_STORE} from '../constants';
4
5
  import {
5
6
  emityProxyValues,
@@ -8,7 +9,7 @@ import {
8
9
  setValueInProxy,
9
10
  } from '../helpers/proxy';
10
11
  import {getValue} from '../helpers/value';
11
- import type {ReactiveOptions, Unsubscribe} from '../models';
12
+ import type {ReactiveOptions, ReactiveState, Unsubscribe} from '../models';
12
13
  import {noop, subscribe} from '../subscription';
13
14
  import type {Computed} from './computed';
14
15
  import {Reactive} from './reactive';
@@ -96,14 +97,22 @@ export class Store<Value extends PlainObject> extends Reactive<Value, Value> {
96
97
  * Set the value
97
98
  * @param value New value _(defaults to an empty object)_
98
99
  */
99
- set(value?: Value): void;
100
+ set(
101
+ value?:
102
+ | Value
103
+ | (() => null | undefined | Value | Promise<null | undefined | Value>)
104
+ | Promise<null | undefined | Value>,
105
+ ): void;
100
106
 
101
107
  /**
102
108
  * Set a value by key
103
109
  * @param key Key of the value to set
104
110
  * @param value New value
105
111
  */
106
- set<Key extends keyof Value>(key: Key, value: Value[Key]): void;
112
+ set<Key extends keyof Value>(
113
+ key: Key,
114
+ value: Value[Key] | (() => Value[Key] | Promise<Value[Key]>) | Promise<Value[Key]>,
115
+ ): void;
107
116
 
108
117
  /**
109
118
  * Set a value by key
@@ -113,11 +122,16 @@ export class Store<Value extends PlainObject> extends Reactive<Value, Value> {
113
122
  set(key: Key, value: unknown): void;
114
123
 
115
124
  set(first?: unknown, second?: unknown): void {
116
- if (isKey(first)) {
117
- (this.state.value as PlainObject)[first] = second;
118
- } else if (first == null || isPlainObject(first)) {
119
- setProxyValue(this.state.value, first ?? {});
120
- }
125
+ setProxyValue<Value>(
126
+ false,
127
+ this.state,
128
+ isStoreObject,
129
+ isKey,
130
+ setObject,
131
+ setProperty,
132
+ first,
133
+ second,
134
+ );
121
135
  }
122
136
 
123
137
  /**
@@ -157,14 +171,80 @@ export class Store<Value extends PlainObject> extends Reactive<Value, Value> {
157
171
  * @param callback Callback to update the value
158
172
  */
159
173
  update(callback: (value: Value) => Value): void {
160
- const updated = callback({...this.state.value});
174
+ const updated = callback(this.state.value);
161
175
 
162
176
  if (updated == null || isPlainObject(updated)) {
163
- setProxyValue(this.state.value, updated ?? {});
177
+ setObject(this.state, updated);
178
+ }
179
+ }
180
+ }
181
+
182
+ function isStoreObject<Value extends PlainObject>(value: unknown): value is Value | undefined {
183
+ return value == null || isPlainObject(value);
184
+ }
185
+
186
+ function setObject(state: ReactiveState<PlainObject, never>, value: PlainObject | undefined): void {
187
+ startBatch();
188
+
189
+ const actual = value ?? {};
190
+ const proxy = state.value as PlainObject;
191
+
192
+ const proxyKeys = Object.keys(proxy);
193
+ const actualKeys = Object.keys(actual);
194
+
195
+ let {length} = proxyKeys;
196
+
197
+ for (let index = 0; index < length; index += 1) {
198
+ const key = proxyKeys[index];
199
+
200
+ proxy[key] = actualKeys.includes(key) ? actual[key] : undefined;
201
+ }
202
+
203
+ length = actualKeys.length;
204
+
205
+ for (let index = 0; index < length; index += 1) {
206
+ const key = actualKeys[index];
207
+
208
+ if (!proxyKeys.includes(key)) {
209
+ const keyedValue = actual[key];
210
+
211
+ proxy[key] = keyedValue;
164
212
  }
165
213
  }
214
+
215
+ stopBatch();
216
+ }
217
+
218
+ function setProperty<Value extends PlainObject>(
219
+ state: ReactiveState<Value, Value>,
220
+ key: unknown,
221
+ value: unknown,
222
+ ): void {
223
+ (state.value as PlainObject)[key as Key] = value;
166
224
  }
167
225
 
226
+ /**
227
+ * Create a reactive store from a function result
228
+ * @param value Initial object value
229
+ * @param options Reactivity options
230
+ * @returns Reactive store
231
+ */
232
+ export function store<Value extends PlainObject>(
233
+ value: () => Value | Promise<Value>,
234
+ options?: ReactiveOptions<Value>,
235
+ ): Store<Value>;
236
+
237
+ /**
238
+ * Create a reactive store from a promise
239
+ * @param value Initial object value
240
+ * @param options Reactivity options
241
+ * @returns Reactive store
242
+ */
243
+ export function store<Value extends PlainObject>(
244
+ value: Promise<Value>,
245
+ options?: ReactiveOptions<Value>,
246
+ ): Store<Value>;
247
+
168
248
  /**
169
249
  * Create a reactive store
170
250
  * @param value Initial object value
@@ -174,6 +254,15 @@ export class Store<Value extends PlainObject> extends Reactive<Value, Value> {
174
254
  export function store<Value extends PlainObject>(
175
255
  value: Value,
176
256
  options?: ReactiveOptions<Value>,
257
+ ): Store<Value>;
258
+
259
+ export function store<Value extends PlainObject>(
260
+ value: Value | (() => Value | Promise<Value>) | Promise<Value>,
261
+ options?: ReactiveOptions<Value>,
177
262
  ): Store<Value> {
178
- return new Store((isPlainObject(value) ? value : {}) as Value, options);
263
+ const instance = new Store({} as unknown as Value, options);
264
+
265
+ instance.set(value);
266
+
267
+ return instance;
179
268
  }
@@ -1,20 +0,0 @@
1
- import { n as Batch, t as Active } from "./models-QwNga87R.mjs";
2
-
3
- //#region src/constants.d.ts
4
- declare const ACTIVE: Active;
5
- declare const ARRAY_THRESHOLD = 100;
6
- declare const ARRAY_OFFSET = 25;
7
- declare const ARRAY_PEEK = 10;
8
- declare const BATCH: Batch;
9
- declare const METHODS_AFFECTING_LENGTH: Set<string>;
10
- declare const METHODS_UPDATE: Set<string>;
11
- declare const NAME_ARRAY = "array";
12
- declare const NAME_COMPUTED = "computed";
13
- declare const NAME_EFFECT = "effect";
14
- declare const NAME_MORA = "$mora";
15
- declare const NAME_SIGNAL = "signal";
16
- declare const NAME_STORE = "store";
17
- declare const NAME_ALL: Set<string>;
18
- declare const PROPERTY_LENGTH = "length";
19
- //#endregion
20
- export { BATCH as a, NAME_ALL as c, NAME_EFFECT as d, NAME_MORA as f, PROPERTY_LENGTH as h, ARRAY_THRESHOLD as i, NAME_ARRAY as l, NAME_STORE as m, ARRAY_OFFSET as n, METHODS_AFFECTING_LENGTH as o, NAME_SIGNAL as p, ARRAY_PEEK as r, METHODS_UPDATE as s, ACTIVE as t, NAME_COMPUTED as u };
@@ -1,17 +0,0 @@
1
- import { GenericCallback } from "@oscarpalmer/atoms/models";
2
-
3
- //#region src/effect.d.ts
4
- declare class Effect {
5
- private readonly $mora;
6
- private readonly state;
7
- constructor(callback: GenericCallback);
8
- }
9
- declare function runEffect(effect: Effect): void;
10
- /**
11
- * Create an effect
12
- * @param callback Callback for handling signal effects
13
- * @returns Effect
14
- */
15
- declare function effect(callback: GenericCallback): Effect;
16
- //#endregion
17
- export { effect as n, runEffect as r, Effect as t };
@@ -1,148 +0,0 @@
1
- import { t as Effect } from "./effect-BkupB1p9.mjs";
2
- import { GenericCallback } from "@oscarpalmer/atoms/models";
3
-
4
- //#region src/subscription.d.ts
5
- declare class Subscription {
6
- callback: GenericCallback;
7
- state: ReactiveState<unknown, never>;
8
- constructor(state: ReactiveState<unknown, never>, callback: GenericCallback);
9
- destroy(): void;
10
- }
11
- declare function noop(): void;
12
- declare function subscribe<Value>(state: ReactiveState<Value, never>, callback: (value: Value) => void): Unsubscribe;
13
- declare function unsubscribe<Value>(state: ReactiveState<Value, never>, callback: (value: Value) => void): void;
14
- //#endregion
15
- //#region src/value/reactive.d.ts
16
- declare abstract class Reactive<Value, Equal = Value> {
17
- protected readonly state: ReactiveState<Value, Equal>;
18
- constructor(name: string, value: Value, options?: ReactiveOptions<Equal>);
19
- /**
20
- * Get the value
21
- * @return Current value
22
- */
23
- abstract get(): Value;
24
- /**
25
- * Get the value _(without reactivity)_
26
- * @return Current value
27
- */
28
- peek(): Value;
29
- /**
30
- * Subscribe to changes
31
- * @param callback Callback for changes
32
- * @return Unsubscribe callback
33
- */
34
- subscribe(callback: (value: Value) => void): Unsubscribe;
35
- /**
36
- * JSON representation of the value
37
- * @return JSON value
38
- */
39
- toJSON(): Value;
40
- /**
41
- * String representation of the value
42
- * @return Value as string
43
- */
44
- toString(): string;
45
- /**
46
- * Unsubscribe from changes
47
- * @param callback Callback to unsubscribe
48
- */
49
- unsubscribe(callback: (value: Value) => void): void;
50
- }
51
- //#endregion
52
- //#region src/value/computed.d.ts
53
- declare class Computed<Value> extends Reactive<Value> {
54
- private readonly effect;
55
- constructor(callback: () => Value, options?: ReactiveOptions<Value>);
56
- /**
57
- * @inheritdoc
58
- */
59
- get(): Value;
60
- }
61
- /**
62
- * Create a computed value
63
- * @param callback Callback to compute the value
64
- * @param options Reactivity options
65
- * @returns Computed value
66
- */
67
- declare function computed<Value>(callback: () => Value, options?: ReactiveOptions<Value>): Computed<Value>;
68
- //#endregion
69
- //#region src/value/signal.d.ts
70
- declare class Signal<Value> extends Reactive<Value> {
71
- constructor(value: Value, options?: ReactiveOptions<Value>);
72
- /**
73
- * @inheritdoc
74
- */
75
- get(): Value;
76
- /**
77
- * Set the value
78
- * @param value New value
79
- */
80
- set(value: Value): void;
81
- /**
82
- * Update the value _(based on the current value)_
83
- * @param callback Callback to update the value
84
- */
85
- update(callback: (value: Value) => Value): void;
86
- }
87
- /**
88
- * Create a reactive value
89
- * @param value Initial value
90
- * @param options Reactivity options
91
- * @returns Reactive value
92
- */
93
- declare function signal<Value>(value: Value, options?: ReactiveOptions<Value>): Signal<Value>;
94
- //#endregion
95
- //#region src/models.d.ts
96
- type Active = {
97
- computed?: Computed<unknown>;
98
- effect?: Effect;
99
- };
100
- type Batch = {
101
- depth: number;
102
- handlers: Set<Effect | Subscription>;
103
- };
104
- type ComputedEffect = {
105
- dirty: boolean;
106
- instance: Effect;
107
- };
108
- type EffectState = {
109
- callback: GenericCallback;
110
- };
111
- type InternalComputed = {
112
- readonly effect: ComputedEffect;
113
- readonly state: ReactiveState<unknown, unknown>;
114
- };
115
- type InternalEffect = {
116
- state: EffectState;
117
- };
118
- type ReactiveOptions<Value> = {
119
- /**
120
- * Method for comparing values for equality
121
- * @param first First value
122
- * @param second Second value
123
- * @returns Are the values equal?
124
- * @default Object.is
125
- */
126
- equal?: (first: Value, second: Value) => boolean;
127
- };
128
- type ReactiveState<Value, Equal> = {
129
- computeds: Set<Computed<unknown>>;
130
- effects: Set<Effect>;
131
- equal: (first: Equal, second: Equal) => boolean;
132
- subscriptions: Map<GenericCallback, Subscription>;
133
- value: Value;
134
- };
135
- type SetValueInProxyParameters<Value, Equal> = {
136
- target: Value;
137
- property: PropertyKey;
138
- value: unknown;
139
- state: ReactiveState<Value, Equal>;
140
- isArray: boolean;
141
- length?: Signal<number>;
142
- };
143
- /**
144
- * Unsubscribe from changes
145
- */
146
- type Unsubscribe = () => void;
147
- //#endregion
148
- export { noop as _, InternalComputed as a, ReactiveState as c, Signal as d, signal as f, Subscription as g, Reactive as h, EffectState as i, SetValueInProxyParameters as l, computed as m, Batch as n, InternalEffect as o, Computed as p, ComputedEffect as r, ReactiveOptions as s, Active as t, Unsubscribe as u, subscribe as v, unsubscribe as y };