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