@oscarpalmer/mora 0.9.0 → 0.10.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.
package/src/computed.ts CHANGED
@@ -1,17 +1,19 @@
1
- import {batchDepth} from './batch';
1
+ import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
+ import {batchDepth, batchedHandlers} from './batch';
3
+ import {type Effect, activeEffect, effect, runEffect} from './effect';
2
4
  import {
3
- type Effect,
4
- activeEffect,
5
- dirtyEffects,
6
- effect,
7
- runEffect,
8
- } from './effect';
9
-
10
- type ComputedState<Value> = {
5
+ type Subscription,
6
+ type Unsubscribe,
7
+ subscribe,
8
+ unsubscribe,
9
+ } from './subscription';
10
+
11
+ export type ComputedState<Value> = {
11
12
  computeds: Set<Computed<unknown>>;
12
13
  dirty: boolean;
13
14
  effect: Effect;
14
15
  effects: Set<Effect>;
16
+ subscriptions: Map<GenericCallback, Subscription>;
15
17
  value: Value;
16
18
  };
17
19
 
@@ -22,76 +24,95 @@ export type InternalComputed = {
22
24
  export let activeComputed: Computed<unknown> | undefined;
23
25
 
24
26
  export class Computed<Value> {
25
- private declare readonly $mora: string;
27
+ private declare readonly $mora: string;
26
28
 
27
- private readonly state: ComputedState<Value>;
29
+ private readonly state: ComputedState<Value>;
28
30
 
29
- constructor(callback: () => Value) {
30
- Object.defineProperty(this, '$mora', {
31
- value: 'computed',
32
- });
31
+ constructor(callback: () => Value) {
32
+ Object.defineProperty(this, '$mora', {
33
+ value: 'computed',
34
+ });
33
35
 
34
- this.state = {
35
- computeds: new Set(),
36
- dirty: true,
37
- effect: undefined as never,
38
- effects: new Set(),
39
- value: undefined as never,
40
- };
36
+ this.state = {
37
+ computeds: new Set(),
38
+ dirty: true,
39
+ effect: undefined as never,
40
+ effects: new Set(),
41
+ subscriptions: new Map(),
42
+ value: undefined as never,
43
+ };
41
44
 
42
- this.state.effect = effect(() => {
43
- if (this.state.dirty) {
44
- const previousComputed = activeComputed;
45
+ this.state.effect = effect(() => {
46
+ if (this.state.dirty) {
47
+ const previousComputed = activeComputed;
45
48
 
46
- activeComputed = this;
49
+ activeComputed = this;
47
50
 
48
- const value = callback();
51
+ const value = callback();
49
52
 
50
- activeComputed = previousComputed;
53
+ activeComputed = previousComputed;
51
54
 
52
- if (!Object.is(this.state.value, value)) {
53
- this.state.value = value;
55
+ if (!Object.is(this.state.value, value)) {
56
+ this.state.value = value;
54
57
 
55
- for (const computed of this.state.computeds) {
56
- computed.state.dirty = true;
57
- }
58
+ for (const computed of this.state.computeds) {
59
+ computed.state.dirty = true;
60
+ }
58
61
 
59
- for (const effect of this.state.effects) {
60
- dirtyEffects.add(effect);
61
- }
62
+ for (const effect of this.state.effects) {
63
+ batchedHandlers.add(effect);
62
64
  }
63
65
 
64
- this.state.dirty = false;
66
+ for (const [, subscription] of this.state.subscriptions) {
67
+ subscription.callback(value);
68
+ }
65
69
  }
66
- });
67
- }
68
70
 
69
- /**
70
- * Get the value
71
- */
72
- get(): Value {
73
- if (activeComputed != null && activeComputed !== this) {
74
- this.state.computeds.add(activeComputed);
75
- }
76
-
77
- if (activeEffect != null && activeEffect !== this.state.effect) {
78
- this.state.effects.add(activeEffect);
71
+ this.state.dirty = false;
79
72
  }
73
+ });
74
+ }
80
75
 
81
- if (this.state.dirty && batchDepth === 0) {
82
- runEffect(this.state.effect);
83
- }
76
+ /**
77
+ * Get the value
78
+ */
79
+ get(): Value {
80
+ if (activeComputed != null && activeComputed !== this) {
81
+ this.state.computeds.add(activeComputed);
82
+ }
84
83
 
85
- return this.state.value;
84
+ if (activeEffect != null && activeEffect !== this.state.effect) {
85
+ this.state.effects.add(activeEffect);
86
86
  }
87
87
 
88
- /**
89
- * Get the value _(without reactivity)_
90
- */
91
- peek(): Value {
92
- return this.state.value;
88
+ if (this.state.dirty && batchDepth === 0) {
89
+ runEffect(this.state.effect);
93
90
  }
91
+
92
+ return this.state.value;
93
+ }
94
+
95
+ /**
96
+ * Get the value _(without reactivity)_
97
+ */
98
+ peek(): Value {
99
+ return this.state.value;
100
+ }
101
+
102
+ /**
103
+ * Subscribe to changes
104
+ */
105
+ subscribe(callback: (value: Value) => void): Unsubscribe {
106
+ return subscribe(this.state, callback);
107
+ }
108
+
109
+ /**
110
+ * Unsubscribe from changes
111
+ */
112
+ unsubscribe(callback: (value: Value) => void): void {
113
+ unsubscribe(this.state, callback);
94
114
  }
115
+ }
95
116
 
96
117
  /**
97
118
  * Create a computed value
package/src/effect.ts CHANGED
@@ -46,5 +46,3 @@ export function effect(callback: GenericCallback): Effect {
46
46
  }
47
47
 
48
48
  export let activeEffect: Effect | undefined;
49
-
50
- export const dirtyEffects = new Set<Effect>();
package/src/signal.ts CHANGED
@@ -1,83 +1,110 @@
1
- import {batchDepth, flushEffects} from './batch';
1
+ import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
+ import {batchDepth, batchedHandlers, flushEffects} from './batch';
2
3
  import {type Computed, type InternalComputed, activeComputed} from './computed';
3
- import {type Effect, activeEffect, dirtyEffects} from './effect';
4
-
5
- type SignalState<Value> = {
4
+ import {type Effect, activeEffect} from './effect';
5
+ import {
6
+ type Subscription,
7
+ type Unsubscribe,
8
+ subscribe,
9
+ unsubscribe,
10
+ } from './subscription';
11
+
12
+ export type SignalState<Value> = {
6
13
  computeds: Set<Computed<unknown>>;
7
14
  effects: Set<Effect>;
15
+ subscriptions: Map<GenericCallback, Subscription>;
8
16
  value: Value;
9
17
  };
10
18
 
11
19
  export class Signal<Value> {
12
- private declare readonly $mora: string;
20
+ private declare readonly $mora: string;
21
+
22
+ private readonly state: SignalState<Value>;
13
23
 
14
- private readonly state: SignalState<Value>;
24
+ constructor(value: Value) {
25
+ Object.defineProperty(this, '$mora', {
26
+ value: 'signal',
27
+ });
15
28
 
16
- constructor(value: Value) {
17
- Object.defineProperty(this, '$mora', {
18
- value: 'signal',
19
- });
29
+ this.state = {
30
+ value,
31
+ computeds: new Set(),
32
+ effects: new Set(),
33
+ subscriptions: new Map(),
34
+ };
35
+ }
20
36
 
21
- this.state = {
22
- value,
23
- computeds: new Set(),
24
- effects: new Set(),
25
- };
37
+ /**
38
+ * Get the value
39
+ */
40
+ get(): Value {
41
+ if (activeComputed != null) {
42
+ this.state.computeds.add(activeComputed);
26
43
  }
27
44
 
28
- /**
29
- * Get the value
30
- */
31
- get(): Value {
32
- if (activeComputed != null) {
33
- this.state.computeds.add(activeComputed);
34
- }
45
+ if (activeEffect != null) {
46
+ this.state.effects.add(activeEffect);
47
+ }
35
48
 
36
- if (activeEffect != null) {
37
- this.state.effects.add(activeEffect);
38
- }
49
+ return this.state.value;
50
+ }
39
51
 
40
- return this.state.value;
41
- }
52
+ /**
53
+ * Get the value _(without reactivity)_
54
+ */
55
+ peek(): Value {
56
+ return this.state.value;
57
+ }
42
58
 
43
- /**
44
- * Get the value _(without reactivity)_
45
- */
46
- peek(): Value {
47
- return this.state.value;
59
+ /**
60
+ * Set the value
61
+ */
62
+ set(value: Value): void {
63
+ if (Object.is(this.state.value, value)) {
64
+ return;
48
65
  }
49
66
 
50
- /**
51
- * Set the value
52
- */
53
- set(value: Value): void {
54
- if (Object.is(this.state.value, value)) {
55
- return;
56
- }
67
+ this.state.value = value;
57
68
 
58
- this.state.value = value;
59
-
60
- for (const computed of this.state.computeds) {
61
- (computed as unknown as InternalComputed).state.dirty = true;
62
- }
69
+ for (const computed of this.state.computeds) {
70
+ (computed as unknown as InternalComputed).state.dirty = true;
71
+ }
63
72
 
64
- for (const effect of this.state.effects) {
65
- dirtyEffects.add(effect);
66
- }
73
+ for (const effect of this.state.effects) {
74
+ batchedHandlers.add(effect);
75
+ }
67
76
 
68
- if (batchDepth === 0) {
69
- flushEffects();
70
- }
77
+ for (const [, subscription] of this.state.subscriptions) {
78
+ batchedHandlers.add(subscription);
71
79
  }
72
80
 
73
- /**
74
- * Update the value
75
- */
76
- update(callback: (value: Value) => Value): void {
77
- this.set(callback(this.state.value));
81
+ if (batchDepth === 0) {
82
+ flushEffects();
78
83
  }
79
84
  }
80
85
 
86
+ /**
87
+ * Subscribe to changes
88
+ */
89
+ subscribe(callback: (value: Value) => void): Unsubscribe {
90
+ return subscribe(this.state, callback);
91
+ }
92
+
93
+ /**
94
+ * Unsubscribe from changes
95
+ */
96
+ unsubscribe(callback: (value: Value) => void): void {
97
+ unsubscribe(this.state, callback);
98
+ }
99
+
100
+ /**
101
+ * Update the value
102
+ */
103
+ update(callback: (value: Value) => Value): void {
104
+ this.set(callback(this.state.value));
105
+ }
106
+ }
107
+
81
108
  export function signal<Value>(value: Value): Signal<Value> {
82
109
  return new Signal(value);
83
110
  }
@@ -0,0 +1,45 @@
1
+ import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
+ import type {ComputedState} from './computed';
3
+ import type {SignalState} from './signal';
4
+
5
+ export class Subscription {
6
+ constructor(
7
+ public state: ComputedState<unknown> | SignalState<unknown>,
8
+ public callback: GenericCallback,
9
+ ) {
10
+ callback(state.value);
11
+ }
12
+ }
13
+
14
+ export type Unsubscribe = () => void;
15
+
16
+ export function noop(): void {}
17
+
18
+ export function subscribe(
19
+ state: ComputedState<unknown> | SignalState<unknown>,
20
+ callback: GenericCallback,
21
+ ): Unsubscribe {
22
+ if (typeof callback !== 'function' || state.subscriptions.has(callback)) {
23
+ return noop;
24
+ }
25
+
26
+ state.subscriptions.set(callback, new Subscription(state, callback));
27
+
28
+ return () => {
29
+ unsubscribe(state, callback);
30
+ };
31
+ }
32
+
33
+ export function unsubscribe(
34
+ state: ComputedState<unknown> | SignalState<unknown>,
35
+ callback: GenericCallback,
36
+ ): void {
37
+ const subscription = state.subscriptions.get(callback);
38
+
39
+ state.subscriptions.delete(callback);
40
+
41
+ if (subscription != null) {
42
+ subscription.callback = noop;
43
+ subscription.state = undefined as never;
44
+ }
45
+ }
package/types/batch.d.cts CHANGED
@@ -1,5 +1,52 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
+ export type GenericCallback = (...args: any[]) => any;
4
+ declare class Effect {
5
+ private readonly $mora;
6
+ private readonly state;
7
+ constructor(callback: GenericCallback);
8
+ }
9
+ export type ComputedState<Value> = {
10
+ computeds: Set<Computed<unknown>>;
11
+ dirty: boolean;
12
+ effect: Effect;
13
+ effects: Set<Effect>;
14
+ subscriptions: Map<GenericCallback, Subscription>;
15
+ value: Value;
16
+ };
17
+ declare class Computed<Value> {
18
+ private readonly $mora;
19
+ private readonly state;
20
+ constructor(callback: () => Value);
21
+ /**
22
+ * Get the value
23
+ */
24
+ get(): Value;
25
+ /**
26
+ * Get the value _(without reactivity)_
27
+ */
28
+ peek(): Value;
29
+ /**
30
+ * Subscribe to changes
31
+ */
32
+ subscribe(callback: (value: Value) => void): Unsubscribe;
33
+ /**
34
+ * Unsubscribe from changes
35
+ */
36
+ unsubscribe(callback: (value: Value) => void): void;
37
+ }
38
+ export type SignalState<Value> = {
39
+ computeds: Set<Computed<unknown>>;
40
+ effects: Set<Effect>;
41
+ subscriptions: Map<GenericCallback, Subscription>;
42
+ value: Value;
43
+ };
44
+ declare class Subscription {
45
+ state: ComputedState<unknown> | SignalState<unknown>;
46
+ callback: GenericCallback;
47
+ constructor(state: ComputedState<unknown> | SignalState<unknown>, callback: GenericCallback);
48
+ }
49
+ export type Unsubscribe = () => void;
3
50
  export declare function flushEffects(): void;
4
51
  /**
5
52
  * Start batching effects _(use `stopBatch` to flush and run batched effects)_
@@ -9,6 +56,7 @@ export declare function startBatch(): void;
9
56
  * Stop batching effects and flush _(run)_ them
10
57
  */
11
58
  export declare function stopBatch(): void;
59
+ export declare const batchedHandlers: Set<Effect | Subscription>;
12
60
  export declare let batchDepth: number;
13
61
 
14
62
  export {};
package/types/batch.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { type Effect } from './effect';
2
+ import type { Subscription } from './subscription';
1
3
  export declare function flushEffects(): void;
2
4
  /**
3
5
  * Start batching effects _(use `stopBatch` to flush and run batched effects)_
@@ -7,4 +9,5 @@ export declare function startBatch(): void;
7
9
  * Stop batching effects and flush _(run)_ them
8
10
  */
9
11
  export declare function stopBatch(): void;
12
+ export declare const batchedHandlers: Set<Effect | Subscription>;
10
13
  export declare let batchDepth: number;
@@ -11,6 +11,7 @@ export type ComputedState<Value> = {
11
11
  dirty: boolean;
12
12
  effect: Effect;
13
13
  effects: Set<Effect>;
14
+ subscriptions: Map<GenericCallback, Subscription>;
14
15
  value: Value;
15
16
  };
16
17
  export type InternalComputed = {
@@ -29,10 +30,30 @@ export declare class Computed<Value> {
29
30
  * Get the value _(without reactivity)_
30
31
  */
31
32
  peek(): Value;
33
+ /**
34
+ * Subscribe to changes
35
+ */
36
+ subscribe(callback: (value: Value) => void): Unsubscribe;
37
+ /**
38
+ * Unsubscribe from changes
39
+ */
40
+ unsubscribe(callback: (value: Value) => void): void;
32
41
  }
33
42
  /**
34
43
  * Create a computed value
35
44
  */
36
45
  export declare function computed<Value>(callback: () => Value): Computed<Value>;
46
+ export type SignalState<Value> = {
47
+ computeds: Set<Computed<unknown>>;
48
+ effects: Set<Effect>;
49
+ subscriptions: Map<GenericCallback, Subscription>;
50
+ value: Value;
51
+ };
52
+ declare class Subscription {
53
+ state: ComputedState<unknown> | SignalState<unknown>;
54
+ callback: GenericCallback;
55
+ constructor(state: ComputedState<unknown> | SignalState<unknown>, callback: GenericCallback);
56
+ }
57
+ export type Unsubscribe = () => void;
37
58
 
38
59
  export {};
@@ -1,9 +1,12 @@
1
+ import type { GenericCallback } from '@oscarpalmer/atoms/models';
1
2
  import { type Effect } from './effect';
2
- type ComputedState<Value> = {
3
+ import { type Subscription, type Unsubscribe } from './subscription';
4
+ export type ComputedState<Value> = {
3
5
  computeds: Set<Computed<unknown>>;
4
6
  dirty: boolean;
5
7
  effect: Effect;
6
8
  effects: Set<Effect>;
9
+ subscriptions: Map<GenericCallback, Subscription>;
7
10
  value: Value;
8
11
  };
9
12
  export type InternalComputed = {
@@ -22,9 +25,16 @@ export declare class Computed<Value> {
22
25
  * Get the value _(without reactivity)_
23
26
  */
24
27
  peek(): Value;
28
+ /**
29
+ * Subscribe to changes
30
+ */
31
+ subscribe(callback: (value: Value) => void): Unsubscribe;
32
+ /**
33
+ * Unsubscribe from changes
34
+ */
35
+ unsubscribe(callback: (value: Value) => void): void;
25
36
  }
26
37
  /**
27
38
  * Create a computed value
28
39
  */
29
40
  export declare function computed<Value>(callback: () => Value): Computed<Value>;
30
- export {};
@@ -12,6 +12,5 @@ export declare function runEffect(effect: Effect): void;
12
12
  */
13
13
  export declare function effect(callback: GenericCallback): Effect;
14
14
  export declare let activeEffect: Effect | undefined;
15
- export declare const dirtyEffects: Set<Effect>;
16
15
 
17
16
  export {};
package/types/effect.d.ts CHANGED
@@ -10,4 +10,3 @@ export declare function runEffect(effect: Effect): void;
10
10
  */
11
11
  export declare function effect(callback: GenericCallback): Effect;
12
12
  export declare let activeEffect: Effect | undefined;
13
- export declare const dirtyEffects: Set<Effect>;
package/types/index.d.cts CHANGED
@@ -1,9 +1,19 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
- export declare class Signal<Value> {
3
+ export type GenericCallback = (...args: any[]) => any;
4
+ export declare class Effect {
4
5
  private readonly $mora;
5
6
  private readonly state;
6
- constructor(value: Value);
7
+ constructor(callback: GenericCallback);
8
+ }
9
+ /**
10
+ * Create an effect
11
+ */
12
+ export declare function effect(callback: GenericCallback): Effect;
13
+ export declare class Computed<Value> {
14
+ private readonly $mora;
15
+ private readonly state;
16
+ constructor(callback: () => Value);
7
17
  /**
8
18
  * Get the value
9
19
  */
@@ -13,29 +23,22 @@ export declare class Signal<Value> {
13
23
  */
14
24
  peek(): Value;
15
25
  /**
16
- * Set the value
26
+ * Subscribe to changes
17
27
  */
18
- set(value: Value): void;
28
+ subscribe(callback: (value: Value) => void): Unsubscribe;
19
29
  /**
20
- * Update the value
30
+ * Unsubscribe from changes
21
31
  */
22
- update(callback: (value: Value) => Value): void;
23
- }
24
- export declare function signal<Value>(value: Value): Signal<Value>;
25
- export type GenericCallback = (...args: any[]) => any;
26
- export declare class Effect {
27
- private readonly $mora;
28
- private readonly state;
29
- constructor(callback: GenericCallback);
32
+ unsubscribe(callback: (value: Value) => void): void;
30
33
  }
31
34
  /**
32
- * Create an effect
35
+ * Create a computed value
33
36
  */
34
- export declare function effect(callback: GenericCallback): Effect;
35
- export declare class Computed<Value> {
37
+ export declare function computed<Value>(callback: () => Value): Computed<Value>;
38
+ export declare class Signal<Value> {
36
39
  private readonly $mora;
37
40
  private readonly state;
38
- constructor(callback: () => Value);
41
+ constructor(value: Value);
39
42
  /**
40
43
  * Get the value
41
44
  */
@@ -44,11 +47,25 @@ export declare class Computed<Value> {
44
47
  * Get the value _(without reactivity)_
45
48
  */
46
49
  peek(): Value;
50
+ /**
51
+ * Set the value
52
+ */
53
+ set(value: Value): void;
54
+ /**
55
+ * Subscribe to changes
56
+ */
57
+ subscribe(callback: (value: Value) => void): Unsubscribe;
58
+ /**
59
+ * Unsubscribe from changes
60
+ */
61
+ unsubscribe(callback: (value: Value) => void): void;
62
+ /**
63
+ * Update the value
64
+ */
65
+ update(callback: (value: Value) => Value): void;
47
66
  }
48
- /**
49
- * Create a computed value
50
- */
51
- export declare function computed<Value>(callback: () => Value): Computed<Value>;
67
+ export declare function signal<Value>(value: Value): Signal<Value>;
68
+ export type Unsubscribe = () => void;
52
69
  /**
53
70
  * Is the value a computed signal?
54
71
  */