@oscarpalmer/mora 0.3.0 → 0.5.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/package.json CHANGED
@@ -6,18 +6,18 @@
6
6
  "description": "Signals and stuff…",
7
7
  "devDependencies": {
8
8
  "@biomejs/biome": "^1.9",
9
- "@oscarpalmer/atoms": "^0.82",
9
+ "@oscarpalmer/atoms": "^0.99",
10
10
  "@rollup/plugin-node-resolve": "^16",
11
11
  "@rollup/plugin-typescript": "^12.1",
12
- "@types/node": "^22.13",
13
- "@vitest/coverage-istanbul": "^3",
12
+ "@types/node": "^22.15",
13
+ "@vitest/coverage-istanbul": "^3.1",
14
14
  "dts-bundle-generator": "^9.5",
15
15
  "glob": "^11",
16
- "happy-dom": "^17",
16
+ "jsdom": "^26.1",
17
17
  "tslib": "^2.8",
18
- "typescript": "^5.7",
19
- "vite": "^6",
20
- "vitest": "^3"
18
+ "typescript": "^5.8",
19
+ "vite": "^6.3",
20
+ "vitest": "^3.1"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
@@ -31,16 +31,8 @@
31
31
  }
32
32
  }
33
33
  },
34
- "files": [
35
- "dist",
36
- "src",
37
- "types"
38
- ],
39
- "keywords": [
40
- "signal",
41
- "signals",
42
- "reactive"
43
- ],
34
+ "files": ["dist", "src", "types"],
35
+ "keywords": ["signal", "signals", "reactive"],
44
36
  "license": "MIT",
45
37
  "main": "./dist/index.cjs",
46
38
  "module": "./dist/index.js",
@@ -55,10 +47,10 @@
55
47
  "clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
56
48
  "rollup": "npx rollup -c",
57
49
  "test": "npx vitest --coverage",
58
- "types": "npx tsc && npx dts-bundle-generator --config ./dts.config.cts",
50
+ "types": "npx tsc && npx dts-bundle-generator --config ./dts.config.cts --silent",
59
51
  "watch": "npx vite build --watch"
60
52
  },
61
53
  "type": "module",
62
54
  "types": "./types/index.d.ts",
63
- "version": "0.3.0"
55
+ "version": "0.5.0"
64
56
  }
package/src/batch.ts ADDED
@@ -0,0 +1,35 @@
1
+ import {dirtyEffects, runEffect} from './effect';
2
+
3
+ export function flushEffects(): void {
4
+ while (dirtyEffects.size > 0) {
5
+ const effects = [...dirtyEffects];
6
+
7
+ dirtyEffects.clear();
8
+
9
+ for (const effect of effects) {
10
+ runEffect(effect);
11
+ }
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Start batching effects _(use `stopBatch` to flush and run batched effects)_
17
+ */
18
+ export function startBatch(): void {
19
+ batchDepth += 1;
20
+ }
21
+
22
+ /**
23
+ * Stop batching effects and flush _(run)_ them
24
+ */
25
+ export function stopBatch(): void {
26
+ if (batchDepth > 0) {
27
+ batchDepth -= 1;
28
+ }
29
+
30
+ if (batchDepth === 0) {
31
+ flushEffects();
32
+ }
33
+ }
34
+
35
+ export let batchDepth = 0;
package/src/computed.ts CHANGED
@@ -1,36 +1,85 @@
1
- import {Effect, activeEffect, runEffect} from './effect';
1
+ import {
2
+ type Effect,
3
+ activeEffect,
4
+ dirtyEffects,
5
+ effect,
6
+ runEffect,
7
+ } from './effect';
2
8
 
3
- export class Computed<Value> extends Effect {
4
- dirty = true;
5
- effects = new Set<Effect>();
6
- value!: Value;
9
+ type ComputedState<Value> = {
10
+ computeds: Set<Computed<unknown>>;
11
+ dirty: boolean;
12
+ effect: Effect;
13
+ effects: Set<Effect>;
14
+ value: Value;
15
+ };
7
16
 
8
- constructor(callback: () => Value) {
9
- super();
17
+ export let activeComputed: Computed<unknown> | undefined;
10
18
 
11
- this.callback = () => {
12
- if (this.dirty) {
13
- this.value = callback();
14
- this.dirty = false;
15
- }
19
+ export class Computed<Value> {
20
+ readonly state: ComputedState<Value>;
21
+
22
+ constructor(callback: () => Value) {
23
+ this.state = {
24
+ computeds: new Set(),
25
+ dirty: true,
26
+ effect: undefined as never,
27
+ effects: new Set(),
28
+ value: undefined as never,
16
29
  };
17
30
 
18
- runEffect(this);
31
+ this.state.effect = effect(() => {
32
+ if (this.state.dirty) {
33
+ const previousComputed = activeComputed;
34
+
35
+ activeComputed = this;
36
+
37
+ const value = callback();
38
+
39
+ activeComputed = previousComputed;
40
+
41
+ if (!Object.is(this.state.value, value)) {
42
+ this.state.value = value;
43
+
44
+ for (const comp of this.state.computeds) {
45
+ comp.state.dirty = true;
46
+
47
+ dirtyEffects.add(comp.state.effect);
48
+ }
49
+
50
+ for (const effect of this.state.effects) {
51
+ dirtyEffects.add(effect);
52
+ }
53
+ }
54
+
55
+ this.state.dirty = false;
56
+ }
57
+ });
19
58
  }
20
59
 
60
+ /**
61
+ * Get the value
62
+ */
21
63
  get(): Value {
22
- if (activeEffect != null && activeEffect !== this) {
23
- this.effects.add(activeEffect);
64
+ if (activeComputed != null && activeComputed !== this) {
65
+ this.state.computeds.add(activeComputed);
66
+ }
67
+
68
+ if (activeEffect != null && activeEffect !== this.state.effect) {
69
+ this.state.effects.add(activeEffect);
24
70
  }
25
71
 
26
- if (this.dirty) {
27
- runEffect(this);
72
+ if (this.state.dirty) {
73
+ runEffect(this.state.effect);
28
74
  }
29
75
 
30
- return this.value;
76
+ return this.state.value;
31
77
  }
32
78
  }
33
79
 
80
+ /**
81
+ * Create a computed value
82
+ */
34
83
  export function computed<Value>(callback: () => Value): Computed<Value> {
35
84
  return new Computed(callback);
36
85
  }
package/src/effect.ts CHANGED
@@ -1,38 +1,40 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import type {Signal} from './signal';
3
2
 
4
- export let activeEffect: Effect | undefined;
3
+ type EffectState = {
4
+ callback: GenericCallback;
5
+ };
5
6
 
6
7
  export class Effect {
7
- callback!: GenericCallback;
8
- signals = new Set<Signal<unknown>>();
9
- }
8
+ readonly state: EffectState;
10
9
 
11
- export function runEffect(effect: Effect): void {
12
- for (const signal of effect.signals) {
13
- signal.state.observers.delete(effect);
14
- }
10
+ constructor(callback: GenericCallback) {
11
+ this.state = {
12
+ callback,
13
+ };
15
14
 
16
- effect.signals.clear();
15
+ runEffect(this);
16
+ }
17
+ }
17
18
 
19
+ export function runEffect(effect: Effect): void {
18
20
  const previousEffect = activeEffect;
19
21
 
20
22
  activeEffect = effect;
21
23
 
22
24
  try {
23
- effect.callback();
25
+ effect.state.callback();
24
26
  } finally {
25
27
  activeEffect = previousEffect;
26
28
  }
27
29
  }
28
30
 
31
+ /**
32
+ * Create an effect
33
+ */
29
34
  export function effect(callback: GenericCallback): Effect {
30
- const instance = new Effect();
31
-
32
- instance.callback = callback;
33
-
34
- runEffect(instance);
35
-
36
- return instance;
35
+ return new Effect(callback);
37
36
  }
38
37
 
38
+ export let activeEffect: Effect | undefined;
39
+
40
+ export const dirtyEffects = new Set<Effect>();
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export {startBatch, stopBatch} from './batch';
1
2
  export {computed, type Computed} from './computed';
2
3
  export {effect, type Effect} from './effect';
3
4
  export {signal, type Signal} from './signal';
package/src/signal.ts CHANGED
@@ -1,34 +1,42 @@
1
- import {Computed} from './computed';
2
- import {type Effect, activeEffect, runEffect} from './effect';
1
+ import {batchDepth, flushEffects} from './batch';
2
+ import {type Computed, activeComputed} from './computed';
3
+ import {type Effect, activeEffect, dirtyEffects} from './effect';
3
4
 
4
5
  type SignalState<Value> = {
5
- observers: Set<Computed<never> | Effect>;
6
+ computeds: Set<Computed<unknown>>;
7
+ effects: Set<Effect>;
6
8
  value: Value;
7
9
  };
8
10
 
9
- const dirtyEffects = new Set<Effect>();
10
-
11
- let batchDepth = 0;
12
-
13
11
  export class Signal<Value> {
14
12
  readonly state: SignalState<Value>;
15
13
 
16
14
  constructor(value: Value) {
17
15
  this.state = {
18
16
  value,
19
- observers: new Set(),
17
+ computeds: new Set(),
18
+ effects: new Set(),
20
19
  };
21
20
  }
22
21
 
22
+ /**
23
+ * Get the value
24
+ */
23
25
  get(): Value {
26
+ if (activeComputed != null) {
27
+ this.state.computeds.add(activeComputed);
28
+ }
29
+
24
30
  if (activeEffect != null) {
25
- this.state.observers.add(activeEffect);
26
- activeEffect.signals.add(this as never);
31
+ this.state.effects.add(activeEffect);
27
32
  }
28
33
 
29
34
  return this.state.value;
30
35
  }
31
36
 
37
+ /**
38
+ * Set the value
39
+ */
32
40
  set(value: Value): void {
33
41
  if (Object.is(this.state.value, value)) {
34
42
  return;
@@ -36,45 +44,24 @@ export class Signal<Value> {
36
44
 
37
45
  this.state.value = value;
38
46
 
39
- const isOutermostBatch = batchDepth === 0;
40
-
41
- batchDepth += 1;
47
+ for (const computed of this.state.computeds) {
48
+ computed.state.dirty = true;
42
49
 
43
- try {
44
- for (const observer of this.state.observers) {
45
- if (observer instanceof Computed) {
46
- observer.dirty = true;
47
-
48
- dirtyEffects.add(observer);
49
-
50
- for (const effect of observer.effects) {
51
- dirtyEffects.add(effect);
52
- }
53
- } else {
54
- dirtyEffects.add(observer);
55
- }
56
- }
50
+ dirtyEffects.add(computed.state.effect);
51
+ }
57
52
 
58
- if (isOutermostBatch) {
59
- queueMicrotask(() => {
60
- try {
61
- for (const effect of dirtyEffects) {
62
- runEffect(effect);
63
- }
64
- } finally {
65
- dirtyEffects.clear();
53
+ for (const effect of this.state.effects) {
54
+ dirtyEffects.add(effect);
55
+ }
66
56
 
67
- batchDepth = 0;
68
- }
69
- });
70
- }
71
- } finally {
72
- if (isOutermostBatch) {
73
- batchDepth -= 1;
74
- }
57
+ if (batchDepth === 0) {
58
+ flushEffects();
75
59
  }
76
60
  }
77
61
 
62
+ /**
63
+ * Update the value
64
+ */
78
65
  update(callback: (value: Value) => Value): void {
79
66
  this.set(callback(this.state.value));
80
67
  }
@@ -0,0 +1,14 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ export declare function flushEffects(): void;
4
+ /**
5
+ * Start batching effects _(use `stopBatch` to flush and run batched effects)_
6
+ */
7
+ export declare function startBatch(): void;
8
+ /**
9
+ * Stop batching effects and flush _(run)_ them
10
+ */
11
+ export declare function stopBatch(): void;
12
+ export declare let batchDepth: number;
13
+
14
+ export {};
@@ -0,0 +1,10 @@
1
+ export declare function flushEffects(): void;
2
+ /**
3
+ * Start batching effects _(use `stopBatch` to flush and run batched effects)_
4
+ */
5
+ export declare function startBatch(): void;
6
+ /**
7
+ * Stop batching effects and flush _(run)_ them
8
+ */
9
+ export declare function stopBatch(): void;
10
+ export declare let batchDepth: number;
@@ -1,28 +1,32 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
3
  export type GenericCallback = (...args: any[]) => any;
4
- declare class Effect {
4
+ export type EffectState = {
5
5
  callback: GenericCallback;
6
- signals: Set<Signal<unknown>>;
6
+ };
7
+ declare class Effect {
8
+ readonly state: EffectState;
9
+ constructor(callback: GenericCallback);
7
10
  }
8
- export declare class Computed<Value> extends Effect {
11
+ export type ComputedState<Value> = {
12
+ computeds: Set<Computed<unknown>>;
9
13
  dirty: boolean;
14
+ effect: Effect;
10
15
  effects: Set<Effect>;
11
16
  value: Value;
17
+ };
18
+ export declare let activeComputed: Computed<unknown> | undefined;
19
+ export declare class Computed<Value> {
20
+ readonly state: ComputedState<Value>;
12
21
  constructor(callback: () => Value);
22
+ /**
23
+ * Get the value
24
+ */
13
25
  get(): Value;
14
26
  }
27
+ /**
28
+ * Create a computed value
29
+ */
15
30
  export declare function computed<Value>(callback: () => Value): Computed<Value>;
16
- export type SignalState<Value> = {
17
- observers: Set<Computed<never> | Effect>;
18
- value: Value;
19
- };
20
- declare class Signal<Value> {
21
- readonly state: SignalState<Value>;
22
- constructor(value: Value);
23
- get(): Value;
24
- set(value: Value): void;
25
- update(callback: (value: Value) => Value): void;
26
- }
27
31
 
28
32
  export {};
@@ -1,9 +1,22 @@
1
- import { Effect } from './effect';
2
- export declare class Computed<Value> extends Effect {
1
+ import { type Effect } from './effect';
2
+ type ComputedState<Value> = {
3
+ computeds: Set<Computed<unknown>>;
3
4
  dirty: boolean;
5
+ effect: Effect;
4
6
  effects: Set<Effect>;
5
7
  value: Value;
8
+ };
9
+ export declare let activeComputed: Computed<unknown> | undefined;
10
+ export declare class Computed<Value> {
11
+ readonly state: ComputedState<Value>;
6
12
  constructor(callback: () => Value);
13
+ /**
14
+ * Get the value
15
+ */
7
16
  get(): Value;
8
17
  }
18
+ /**
19
+ * Create a computed value
20
+ */
9
21
  export declare function computed<Value>(callback: () => Value): Computed<Value>;
22
+ export {};
@@ -1,30 +1,19 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
3
  export type GenericCallback = (...args: any[]) => any;
4
- export declare let activeEffect: Effect | undefined;
5
- export declare class Effect {
4
+ export type EffectState = {
6
5
  callback: GenericCallback;
7
- signals: Set<Signal<unknown>>;
6
+ };
7
+ export declare class Effect {
8
+ readonly state: EffectState;
9
+ constructor(callback: GenericCallback);
8
10
  }
9
11
  export declare function runEffect(effect: Effect): void;
12
+ /**
13
+ * Create an effect
14
+ */
10
15
  export declare function effect(callback: GenericCallback): Effect;
11
- declare class Computed<Value> extends Effect {
12
- dirty: boolean;
13
- effects: Set<Effect>;
14
- value: Value;
15
- constructor(callback: () => Value);
16
- get(): Value;
17
- }
18
- export type SignalState<Value> = {
19
- observers: Set<Computed<never> | Effect>;
20
- value: Value;
21
- };
22
- declare class Signal<Value> {
23
- readonly state: SignalState<Value>;
24
- constructor(value: Value);
25
- get(): Value;
26
- set(value: Value): void;
27
- update(callback: (value: Value) => Value): void;
28
- }
16
+ export declare let activeEffect: Effect | undefined;
17
+ export declare const dirtyEffects: Set<Effect>;
29
18
 
30
19
  export {};
package/types/effect.d.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  import type { GenericCallback } from '@oscarpalmer/atoms/models';
2
- import type { Signal } from './signal';
3
- export declare let activeEffect: Effect | undefined;
4
- export declare class Effect {
2
+ type EffectState = {
5
3
  callback: GenericCallback;
6
- signals: Set<Signal<unknown>>;
4
+ };
5
+ export declare class Effect {
6
+ readonly state: EffectState;
7
+ constructor(callback: GenericCallback);
7
8
  }
8
9
  export declare function runEffect(effect: Effect): void;
10
+ /**
11
+ * Create an effect
12
+ */
9
13
  export declare function effect(callback: GenericCallback): Effect;
14
+ export declare let activeEffect: Effect | undefined;
15
+ export declare const dirtyEffects: Set<Effect>;
16
+ export {};
package/types/index.d.cts CHANGED
@@ -1,30 +1,65 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
3
  export type GenericCallback = (...args: any[]) => any;
4
- export declare class Effect {
4
+ export type EffectState = {
5
5
  callback: GenericCallback;
6
- signals: Set<Signal<unknown>>;
6
+ };
7
+ export declare class Effect {
8
+ readonly state: EffectState;
9
+ constructor(callback: GenericCallback);
7
10
  }
11
+ /**
12
+ * Create an effect
13
+ */
8
14
  export declare function effect(callback: GenericCallback): Effect;
9
- export declare class Computed<Value> extends Effect {
15
+ export type ComputedState<Value> = {
16
+ computeds: Set<Computed<unknown>>;
10
17
  dirty: boolean;
18
+ effect: Effect;
11
19
  effects: Set<Effect>;
12
20
  value: Value;
21
+ };
22
+ export declare class Computed<Value> {
23
+ readonly state: ComputedState<Value>;
13
24
  constructor(callback: () => Value);
25
+ /**
26
+ * Get the value
27
+ */
14
28
  get(): Value;
15
29
  }
30
+ /**
31
+ * Create a computed value
32
+ */
16
33
  export declare function computed<Value>(callback: () => Value): Computed<Value>;
17
34
  export type SignalState<Value> = {
18
- observers: Set<Computed<never> | Effect>;
35
+ computeds: Set<Computed<unknown>>;
36
+ effects: Set<Effect>;
19
37
  value: Value;
20
38
  };
21
39
  export declare class Signal<Value> {
22
40
  readonly state: SignalState<Value>;
23
41
  constructor(value: Value);
42
+ /**
43
+ * Get the value
44
+ */
24
45
  get(): Value;
46
+ /**
47
+ * Set the value
48
+ */
25
49
  set(value: Value): void;
50
+ /**
51
+ * Update the value
52
+ */
26
53
  update(callback: (value: Value) => Value): void;
27
54
  }
28
55
  export declare function signal<Value>(value: Value): Signal<Value>;
56
+ /**
57
+ * Start batching effects _(use `stopBatch` to flush and run batched effects)_
58
+ */
59
+ export declare function startBatch(): void;
60
+ /**
61
+ * Stop batching effects and flush _(run)_ them
62
+ */
63
+ export declare function stopBatch(): void;
29
64
 
30
65
  export {};
package/types/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { startBatch, stopBatch } from './batch';
1
2
  export { computed, type Computed } from './computed';
2
3
  export { effect, type Effect } from './effect';
3
4
  export { signal, type Signal } from './signal';
@@ -1,26 +1,47 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
3
  export type GenericCallback = (...args: any[]) => any;
4
- declare class Effect {
4
+ export type EffectState = {
5
5
  callback: GenericCallback;
6
- signals: Set<Signal<unknown>>;
6
+ };
7
+ declare class Effect {
8
+ readonly state: EffectState;
9
+ constructor(callback: GenericCallback);
7
10
  }
8
- declare class Computed<Value> extends Effect {
11
+ export type ComputedState<Value> = {
12
+ computeds: Set<Computed<unknown>>;
9
13
  dirty: boolean;
14
+ effect: Effect;
10
15
  effects: Set<Effect>;
11
16
  value: Value;
17
+ };
18
+ declare class Computed<Value> {
19
+ readonly state: ComputedState<Value>;
12
20
  constructor(callback: () => Value);
21
+ /**
22
+ * Get the value
23
+ */
13
24
  get(): Value;
14
25
  }
15
26
  export type SignalState<Value> = {
16
- observers: Set<Computed<never> | Effect>;
27
+ computeds: Set<Computed<unknown>>;
28
+ effects: Set<Effect>;
17
29
  value: Value;
18
30
  };
19
31
  export declare class Signal<Value> {
20
32
  readonly state: SignalState<Value>;
21
33
  constructor(value: Value);
34
+ /**
35
+ * Get the value
36
+ */
22
37
  get(): Value;
38
+ /**
39
+ * Set the value
40
+ */
23
41
  set(value: Value): void;
42
+ /**
43
+ * Update the value
44
+ */
24
45
  update(callback: (value: Value) => Value): void;
25
46
  }
26
47
  export declare function signal<Value>(value: Value): Signal<Value>;