@oscarpalmer/mora 0.9.0 → 0.11.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/dist/signal.js CHANGED
@@ -1,20 +1,10 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { flushEffects, batchDepth } from "./batch.js";
1
+ import { batchedHandlers, flushEffects, batchDepth } from "./batch.js";
5
2
  import { activeComputed } from "./computed.js";
6
- import { activeEffect, dirtyEffects } from "./effect.js";
7
- class Signal {
3
+ import { activeEffect } from "./effect.js";
4
+ import { Reactive } from "./reactive.js";
5
+ class Signal extends Reactive {
8
6
  constructor(value) {
9
- __publicField(this, "state");
10
- Object.defineProperty(this, "$mora", {
11
- value: "signal"
12
- });
13
- this.state = {
14
- value,
15
- computeds: /* @__PURE__ */ new Set(),
16
- effects: /* @__PURE__ */ new Set()
17
- };
7
+ super("signal", value);
18
8
  }
19
9
  /**
20
10
  * Get the value
@@ -28,12 +18,6 @@ class Signal {
28
18
  }
29
19
  return this.state.value;
30
20
  }
31
- /**
32
- * Get the value _(without reactivity)_
33
- */
34
- peek() {
35
- return this.state.value;
36
- }
37
21
  /**
38
22
  * Set the value
39
23
  */
@@ -43,10 +27,13 @@ class Signal {
43
27
  }
44
28
  this.state.value = value;
45
29
  for (const computed of this.state.computeds) {
46
- computed.state.dirty = true;
30
+ computed.effect.dirty = true;
47
31
  }
48
32
  for (const effect of this.state.effects) {
49
- dirtyEffects.add(effect);
33
+ batchedHandlers.add(effect);
34
+ }
35
+ for (const [, subscription] of this.state.subscriptions) {
36
+ batchedHandlers.add(subscription);
50
37
  }
51
38
  if (batchDepth === 0) {
52
39
  flushEffects();
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ class Subscription {
4
+ constructor(state, callback) {
5
+ this.state = state;
6
+ this.callback = callback;
7
+ callback(state.value);
8
+ }
9
+ }
10
+ function noop() {
11
+ }
12
+ function subscribe(state, callback) {
13
+ if (typeof callback !== "function" || state.subscriptions.has(callback)) {
14
+ return noop;
15
+ }
16
+ state.subscriptions.set(callback, new Subscription(state, callback));
17
+ return () => {
18
+ unsubscribe(state, callback);
19
+ };
20
+ }
21
+ function unsubscribe(state, callback) {
22
+ const subscription = state.subscriptions.get(callback);
23
+ state.subscriptions.delete(callback);
24
+ if (subscription != null) {
25
+ subscription.callback = noop;
26
+ subscription.state = void 0;
27
+ }
28
+ }
29
+ exports.Subscription = Subscription;
30
+ exports.noop = noop;
31
+ exports.subscribe = subscribe;
32
+ exports.unsubscribe = unsubscribe;
@@ -0,0 +1,32 @@
1
+ class Subscription {
2
+ constructor(state, callback) {
3
+ this.state = state;
4
+ this.callback = callback;
5
+ callback(state.value);
6
+ }
7
+ }
8
+ function noop() {
9
+ }
10
+ function subscribe(state, callback) {
11
+ if (typeof callback !== "function" || state.subscriptions.has(callback)) {
12
+ return noop;
13
+ }
14
+ state.subscriptions.set(callback, new Subscription(state, callback));
15
+ return () => {
16
+ unsubscribe(state, callback);
17
+ };
18
+ }
19
+ function unsubscribe(state, callback) {
20
+ const subscription = state.subscriptions.get(callback);
21
+ state.subscriptions.delete(callback);
22
+ if (subscription != null) {
23
+ subscription.callback = noop;
24
+ subscription.state = void 0;
25
+ }
26
+ }
27
+ export {
28
+ Subscription,
29
+ noop,
30
+ subscribe,
31
+ unsubscribe
32
+ };
package/package.json CHANGED
@@ -54,5 +54,5 @@
54
54
  },
55
55
  "type": "module",
56
56
  "types": "./types/index.d.ts",
57
- "version": "0.9.0"
57
+ "version": "0.11.0"
58
58
  }
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<unknown>>();
40
+
33
41
  export let batchDepth = 0;
package/src/computed.ts CHANGED
@@ -1,97 +1,78 @@
1
- import {batchDepth} from './batch';
2
- import {
3
- type Effect,
4
- activeEffect,
5
- dirtyEffects,
6
- effect,
7
- runEffect,
8
- } from './effect';
9
-
10
- type ComputedState<Value> = {
11
- computeds: Set<Computed<unknown>>;
1
+ import {batchDepth, batchedHandlers} from './batch';
2
+ import {type Effect, activeEffect, effect, runEffect} from './effect';
3
+ import {Reactive, type ReactiveState} from './reactive';
4
+
5
+ type ComputedEffect = {
12
6
  dirty: boolean;
13
- effect: Effect;
14
- effects: Set<Effect>;
15
- value: Value;
7
+ instance: Effect;
16
8
  };
17
9
 
18
10
  export type InternalComputed = {
19
- readonly state: ComputedState<unknown>;
11
+ readonly effect: ComputedEffect;
12
+ readonly state: ReactiveState<unknown>;
20
13
  };
21
14
 
22
15
  export let activeComputed: Computed<unknown> | undefined;
23
16
 
24
- export class Computed<Value> {
25
- private declare readonly $mora: string;
26
-
27
- private readonly state: ComputedState<Value>;
17
+ export class Computed<Value> extends Reactive<Value> {
18
+ private readonly effect: ComputedEffect = {
19
+ dirty: true,
20
+ instance: undefined as never,
21
+ };
28
22
 
29
- constructor(callback: () => Value) {
30
- Object.defineProperty(this, '$mora', {
31
- value: 'computed',
32
- });
23
+ constructor(callback: () => Value) {
24
+ super('computed', undefined as never);
33
25
 
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
- };
26
+ this.effect.instance = effect(() => {
27
+ if (this.effect.dirty) {
28
+ const previousComputed = activeComputed;
41
29
 
42
- this.state.effect = effect(() => {
43
- if (this.state.dirty) {
44
- const previousComputed = activeComputed;
30
+ activeComputed = this as never;
45
31
 
46
- activeComputed = this;
32
+ const value = callback();
47
33
 
48
- const value = callback();
34
+ activeComputed = previousComputed;
49
35
 
50
- activeComputed = previousComputed;
36
+ if (!Object.is(this.state.value, value)) {
37
+ this.state.value = value;
51
38
 
52
- if (!Object.is(this.state.value, value)) {
53
- this.state.value = value;
54
-
55
- for (const computed of this.state.computeds) {
56
- computed.state.dirty = true;
57
- }
39
+ for (const computed of this.state.computeds) {
40
+ computed.effect.dirty = true;
41
+ }
58
42
 
59
- for (const effect of this.state.effects) {
60
- dirtyEffects.add(effect);
61
- }
43
+ for (const effect of this.state.effects) {
44
+ batchedHandlers.add(effect);
62
45
  }
63
46
 
64
- this.state.dirty = false;
47
+ for (const [, subscription] of this.state.subscriptions) {
48
+ subscription.callback(value);
49
+ }
65
50
  }
66
- });
67
- }
68
51
 
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);
52
+ this.effect.dirty = false;
79
53
  }
54
+ });
55
+ }
80
56
 
81
- if (this.state.dirty && batchDepth === 0) {
82
- runEffect(this.state.effect);
83
- }
57
+ /**
58
+ * Get the value
59
+ */
60
+ get(): Value {
61
+ if (activeComputed != null && activeComputed !== this) {
62
+ this.state.computeds.add(activeComputed);
63
+ }
84
64
 
85
- return this.state.value;
65
+ if (activeEffect != null && activeEffect !== this.effect.instance) {
66
+ this.state.effects.add(activeEffect);
86
67
  }
87
68
 
88
- /**
89
- * Get the value _(without reactivity)_
90
- */
91
- peek(): Value {
92
- return this.state.value;
69
+ if (this.effect.dirty && batchDepth === 0) {
70
+ runEffect(this.effect.instance);
93
71
  }
72
+
73
+ return this.state.value;
94
74
  }
75
+ }
95
76
 
96
77
  /**
97
78
  * 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/is.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type {PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import type {Computed} from './computed';
3
3
  import type {Effect} from './effect';
4
+ import type {Reactive} from './reactive';
4
5
  import type {Signal} from './signal';
5
6
 
6
7
  /**
@@ -26,6 +27,10 @@ function isMora<T>(value: unknown, name: string): value is T {
26
27
  );
27
28
  }
28
29
 
30
+ export function isReactive(value: unknown): value is Reactive<unknown> {
31
+ return isComputed(value) || isSignal(value);
32
+ }
33
+
29
34
  /**
30
35
  * Is the value a signal?
31
36
  */
@@ -0,0 +1,53 @@
1
+ import type {Computed} from './computed';
2
+ import type {Effect} from './effect';
3
+ import {
4
+ type Subscription,
5
+ type Unsubscribe,
6
+ subscribe,
7
+ unsubscribe,
8
+ } from './subscription';
9
+
10
+ export abstract class Reactive<Value> {
11
+ protected readonly state: ReactiveState<Value> = {
12
+ computeds: new Set(),
13
+ effects: new Set(),
14
+ subscriptions: new Map(),
15
+ value: undefined as never,
16
+ };
17
+
18
+ constructor(name: string, value: Value) {
19
+ this.state.value = value;
20
+
21
+ Object.defineProperty(this, '$mora', {
22
+ value: name,
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Get the value _(without reactivity)_
28
+ */
29
+ peek(): Value {
30
+ return this.state.value;
31
+ }
32
+
33
+ /**
34
+ * Subscribe to changes
35
+ */
36
+ subscribe(callback: (value: Value) => void): Unsubscribe {
37
+ return subscribe(this.state, callback);
38
+ }
39
+
40
+ /**
41
+ * Unsubscribe from changes
42
+ */
43
+ unsubscribe(callback: (value: Value) => void): void {
44
+ unsubscribe(this.state, callback);
45
+ }
46
+ }
47
+
48
+ export type ReactiveState<Value> = {
49
+ computeds: Set<Computed<unknown>>;
50
+ effects: Set<Effect>;
51
+ subscriptions: Map<(value: Value) => void, Subscription<Value>>;
52
+ value: Value;
53
+ };
package/src/signal.ts CHANGED
@@ -1,83 +1,63 @@
1
- import {batchDepth, flushEffects} from './batch';
2
- import {type Computed, type InternalComputed, activeComputed} from './computed';
3
- import {type Effect, activeEffect, dirtyEffects} from './effect';
4
-
5
- type SignalState<Value> = {
6
- computeds: Set<Computed<unknown>>;
7
- effects: Set<Effect>;
8
- value: Value;
9
- };
1
+ import {batchDepth, batchedHandlers, flushEffects} from './batch';
2
+ import {type InternalComputed, activeComputed} from './computed';
3
+ import {activeEffect} from './effect';
4
+ import {Reactive} from './reactive';
5
+
6
+ export class Signal<Value> extends Reactive<Value> {
7
+ constructor(value: Value) {
8
+ super('signal', value);
9
+ }
10
10
 
11
- export class Signal<Value> {
12
- private declare readonly $mora: string;
11
+ /**
12
+ * Get the value
13
+ */
14
+ get(): Value {
15
+ if (activeComputed != null) {
16
+ this.state.computeds.add(activeComputed);
17
+ }
13
18
 
14
- private readonly state: SignalState<Value>;
19
+ if (activeEffect != null) {
20
+ this.state.effects.add(activeEffect);
21
+ }
15
22
 
16
- constructor(value: Value) {
17
- Object.defineProperty(this, '$mora', {
18
- value: 'signal',
19
- });
23
+ return this.state.value;
24
+ }
20
25
 
21
- this.state = {
22
- value,
23
- computeds: new Set(),
24
- effects: new Set(),
25
- };
26
+ /**
27
+ * Set the value
28
+ */
29
+ set(value: Value): void {
30
+ if (Object.is(this.state.value, value)) {
31
+ return;
26
32
  }
27
33
 
28
- /**
29
- * Get the value
30
- */
31
- get(): Value {
32
- if (activeComputed != null) {
33
- this.state.computeds.add(activeComputed);
34
- }
35
-
36
- if (activeEffect != null) {
37
- this.state.effects.add(activeEffect);
38
- }
34
+ this.state.value = value;
39
35
 
40
- return this.state.value;
36
+ for (const computed of this.state.computeds) {
37
+ (computed as unknown as InternalComputed).effect.dirty = true;
41
38
  }
42
39
 
43
- /**
44
- * Get the value _(without reactivity)_
45
- */
46
- peek(): Value {
47
- return this.state.value;
40
+ for (const effect of this.state.effects) {
41
+ batchedHandlers.add(effect);
48
42
  }
49
43
 
50
- /**
51
- * Set the value
52
- */
53
- set(value: Value): void {
54
- if (Object.is(this.state.value, value)) {
55
- return;
56
- }
57
-
58
- this.state.value = value;
59
-
60
- for (const computed of this.state.computeds) {
61
- (computed as unknown as InternalComputed).state.dirty = true;
62
- }
63
-
64
- for (const effect of this.state.effects) {
65
- dirtyEffects.add(effect);
66
- }
67
-
68
- if (batchDepth === 0) {
69
- flushEffects();
70
- }
44
+ for (const [, subscription] of this.state.subscriptions) {
45
+ batchedHandlers.add(subscription as never);
71
46
  }
72
47
 
73
- /**
74
- * Update the value
75
- */
76
- update(callback: (value: Value) => Value): void {
77
- this.set(callback(this.state.value));
48
+ if (batchDepth === 0) {
49
+ flushEffects();
78
50
  }
79
51
  }
80
52
 
53
+ /**
54
+ * Update the value
55
+ */
56
+ update(callback: (value: Value) => Value): void {
57
+ this.set(callback(this.state.value));
58
+ }
59
+ }
60
+
81
61
  export function signal<Value>(value: Value): Signal<Value> {
82
62
  return new Signal(value);
83
63
  }
@@ -0,0 +1,47 @@
1
+ import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
+ import type {ReactiveState} from './reactive';
3
+
4
+ export class Subscription<Value> {
5
+ constructor(
6
+ public state: ReactiveState<Value>,
7
+ public callback: GenericCallback,
8
+ ) {
9
+ callback(state.value);
10
+ }
11
+ }
12
+
13
+ /**
14
+ * Unsubscribe from changes
15
+ */
16
+ export type Unsubscribe = () => void;
17
+
18
+ export function noop(): void {}
19
+
20
+ export function subscribe<Value>(
21
+ state: ReactiveState<Value>,
22
+ callback: (value: Value) => void,
23
+ ): Unsubscribe {
24
+ if (typeof callback !== 'function' || state.subscriptions.has(callback)) {
25
+ return noop;
26
+ }
27
+
28
+ state.subscriptions.set(callback, new Subscription(state, callback));
29
+
30
+ return () => {
31
+ unsubscribe(state, callback);
32
+ };
33
+ }
34
+
35
+ export function unsubscribe<Value>(
36
+ state: ReactiveState<Value>,
37
+ callback: (value: Value) => void,
38
+ ): void {
39
+ const subscription = state.subscriptions.get(callback);
40
+
41
+ state.subscriptions.delete(callback);
42
+
43
+ if (subscription != null) {
44
+ subscription.callback = noop;
45
+ subscription.state = undefined as never;
46
+ }
47
+ }
package/types/batch.d.cts CHANGED
@@ -1,5 +1,50 @@
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
+ declare class Computed<Value> extends Reactive<Value> {
10
+ private readonly effect;
11
+ constructor(callback: () => Value);
12
+ /**
13
+ * Get the value
14
+ */
15
+ get(): Value;
16
+ }
17
+ declare abstract class Reactive<Value> {
18
+ protected readonly state: ReactiveState<Value>;
19
+ constructor(name: string, value: Value);
20
+ /**
21
+ * Get the value _(without reactivity)_
22
+ */
23
+ peek(): Value;
24
+ /**
25
+ * Subscribe to changes
26
+ */
27
+ subscribe(callback: (value: Value) => void): Unsubscribe;
28
+ /**
29
+ * Unsubscribe from changes
30
+ */
31
+ unsubscribe(callback: (value: Value) => void): void;
32
+ }
33
+ export type ReactiveState<Value> = {
34
+ computeds: Set<Computed<unknown>>;
35
+ effects: Set<Effect>;
36
+ subscriptions: Map<(value: Value) => void, Subscription<Value>>;
37
+ value: Value;
38
+ };
39
+ declare class Subscription<Value> {
40
+ state: ReactiveState<Value>;
41
+ callback: GenericCallback;
42
+ constructor(state: ReactiveState<Value>, callback: GenericCallback);
43
+ }
44
+ /**
45
+ * Unsubscribe from changes
46
+ */
47
+ export type Unsubscribe = () => void;
3
48
  export declare function flushEffects(): void;
4
49
  /**
5
50
  * Start batching effects _(use `stopBatch` to flush and run batched effects)_
@@ -9,6 +54,7 @@ export declare function startBatch(): void;
9
54
  * Stop batching effects and flush _(run)_ them
10
55
  */
11
56
  export declare function stopBatch(): void;
57
+ export declare const batchedHandlers: Set<Effect | Subscription<unknown>>;
12
58
  export declare let batchDepth: number;
13
59
 
14
60
  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<unknown>>;
10
13
  export declare let batchDepth: number;