@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/dist/batch.cjs CHANGED
@@ -1,12 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const effect = require("./effect.cjs");
4
+ const is = require("./is.cjs");
4
5
  function flushEffects() {
5
- while (exports.batchDepth === 0 && effect.dirtyEffects.size > 0) {
6
- const effects = [...effect.dirtyEffects];
7
- effect.dirtyEffects.clear();
8
- for (const effect$1 of effects) {
9
- effect.runEffect(effect$1);
6
+ while (exports.batchDepth === 0 && batchedHandlers.size > 0) {
7
+ const handlers = [...batchedHandlers];
8
+ batchedHandlers.clear();
9
+ for (const handler of handlers) {
10
+ if (is.isEffect(handler)) {
11
+ effect.runEffect(handler);
12
+ } else {
13
+ handler.callback(handler.state.value);
14
+ }
10
15
  }
11
16
  }
12
17
  }
@@ -19,7 +24,9 @@ function stopBatch() {
19
24
  }
20
25
  flushEffects();
21
26
  }
27
+ const batchedHandlers = /* @__PURE__ */ new Set();
22
28
  exports.batchDepth = 0;
29
+ exports.batchedHandlers = batchedHandlers;
23
30
  exports.flushEffects = flushEffects;
24
31
  exports.startBatch = startBatch;
25
32
  exports.stopBatch = stopBatch;
package/dist/batch.js CHANGED
@@ -1,10 +1,15 @@
1
- import { dirtyEffects, runEffect } from "./effect.js";
1
+ import { runEffect } from "./effect.js";
2
+ import { isEffect } from "./is.js";
2
3
  function flushEffects() {
3
- while (batchDepth === 0 && dirtyEffects.size > 0) {
4
- const effects = [...dirtyEffects];
5
- dirtyEffects.clear();
6
- for (const effect of effects) {
7
- runEffect(effect);
4
+ while (batchDepth === 0 && batchedHandlers.size > 0) {
5
+ const handlers = [...batchedHandlers];
6
+ batchedHandlers.clear();
7
+ for (const handler of handlers) {
8
+ if (isEffect(handler)) {
9
+ runEffect(handler);
10
+ } else {
11
+ handler.callback(handler.state.value);
12
+ }
8
13
  }
9
14
  }
10
15
  }
@@ -17,9 +22,11 @@ function stopBatch() {
17
22
  }
18
23
  flushEffects();
19
24
  }
25
+ const batchedHandlers = /* @__PURE__ */ new Set();
20
26
  let batchDepth = 0;
21
27
  export {
22
28
  batchDepth,
29
+ batchedHandlers,
23
30
  flushEffects,
24
31
  startBatch,
25
32
  stopBatch
package/dist/computed.cjs CHANGED
@@ -5,15 +5,20 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5
5
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
6
6
  const batch = require("./batch.cjs");
7
7
  const effect = require("./effect.cjs");
8
+ const subscription = require("./subscription.cjs");
8
9
  exports.activeComputed = void 0;
9
10
  class Computed {
10
11
  constructor(callback) {
11
12
  __publicField(this, "state");
13
+ Object.defineProperty(this, "$mora", {
14
+ value: "computed"
15
+ });
12
16
  this.state = {
13
17
  computeds: /* @__PURE__ */ new Set(),
14
18
  dirty: true,
15
19
  effect: void 0,
16
20
  effects: /* @__PURE__ */ new Set(),
21
+ subscriptions: /* @__PURE__ */ new Map(),
17
22
  value: void 0
18
23
  };
19
24
  this.state.effect = effect.effect(() => {
@@ -28,7 +33,10 @@ class Computed {
28
33
  computed2.state.dirty = true;
29
34
  }
30
35
  for (const effect2 of this.state.effects) {
31
- effect.dirtyEffects.add(effect2);
36
+ batch.batchedHandlers.add(effect2);
37
+ }
38
+ for (const [, subscription2] of this.state.subscriptions) {
39
+ subscription2.callback(value);
32
40
  }
33
41
  }
34
42
  this.state.dirty = false;
@@ -56,6 +64,18 @@ class Computed {
56
64
  peek() {
57
65
  return this.state.value;
58
66
  }
67
+ /**
68
+ * Subscribe to changes
69
+ */
70
+ subscribe(callback) {
71
+ return subscription.subscribe(this.state, callback);
72
+ }
73
+ /**
74
+ * Unsubscribe from changes
75
+ */
76
+ unsubscribe(callback) {
77
+ subscription.unsubscribe(this.state, callback);
78
+ }
59
79
  }
60
80
  function computed(callback) {
61
81
  return new Computed(callback);
package/dist/computed.js CHANGED
@@ -1,17 +1,22 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { batchDepth } from "./batch.js";
5
- import { effect, dirtyEffects, activeEffect, runEffect } from "./effect.js";
4
+ import { batchedHandlers, batchDepth } from "./batch.js";
5
+ import { effect, activeEffect, runEffect } from "./effect.js";
6
+ import { subscribe, unsubscribe } from "./subscription.js";
6
7
  let activeComputed;
7
8
  class Computed {
8
9
  constructor(callback) {
9
10
  __publicField(this, "state");
11
+ Object.defineProperty(this, "$mora", {
12
+ value: "computed"
13
+ });
10
14
  this.state = {
11
15
  computeds: /* @__PURE__ */ new Set(),
12
16
  dirty: true,
13
17
  effect: void 0,
14
18
  effects: /* @__PURE__ */ new Set(),
19
+ subscriptions: /* @__PURE__ */ new Map(),
15
20
  value: void 0
16
21
  };
17
22
  this.state.effect = effect(() => {
@@ -26,7 +31,10 @@ class Computed {
26
31
  computed2.state.dirty = true;
27
32
  }
28
33
  for (const effect2 of this.state.effects) {
29
- dirtyEffects.add(effect2);
34
+ batchedHandlers.add(effect2);
35
+ }
36
+ for (const [, subscription] of this.state.subscriptions) {
37
+ subscription.callback(value);
30
38
  }
31
39
  }
32
40
  this.state.dirty = false;
@@ -54,6 +62,18 @@ class Computed {
54
62
  peek() {
55
63
  return this.state.value;
56
64
  }
65
+ /**
66
+ * Subscribe to changes
67
+ */
68
+ subscribe(callback) {
69
+ return subscribe(this.state, callback);
70
+ }
71
+ /**
72
+ * Unsubscribe from changes
73
+ */
74
+ unsubscribe(callback) {
75
+ unsubscribe(this.state, callback);
76
+ }
57
77
  }
58
78
  function computed(callback) {
59
79
  return new Computed(callback);
package/dist/effect.cjs CHANGED
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  class Effect {
4
4
  constructor(callback) {
5
+ Object.defineProperty(this, "$mora", {
6
+ value: "effect"
7
+ });
5
8
  this.state = {
6
9
  callback
7
10
  };
@@ -21,8 +24,6 @@ function effect(callback) {
21
24
  return new Effect(callback);
22
25
  }
23
26
  exports.activeEffect = void 0;
24
- const dirtyEffects = /* @__PURE__ */ new Set();
25
27
  exports.Effect = Effect;
26
- exports.dirtyEffects = dirtyEffects;
27
28
  exports.effect = effect;
28
29
  exports.runEffect = runEffect;
package/dist/effect.js CHANGED
@@ -1,5 +1,8 @@
1
1
  class Effect {
2
2
  constructor(callback) {
3
+ Object.defineProperty(this, "$mora", {
4
+ value: "effect"
5
+ });
3
6
  this.state = {
4
7
  callback
5
8
  };
@@ -19,11 +22,9 @@ function effect(callback) {
19
22
  return new Effect(callback);
20
23
  }
21
24
  let activeEffect;
22
- const dirtyEffects = /* @__PURE__ */ new Set();
23
25
  export {
24
26
  Effect,
25
27
  activeEffect,
26
- dirtyEffects,
27
28
  effect,
28
29
  runEffect
29
30
  };
package/dist/index.cjs CHANGED
@@ -3,9 +3,13 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const batch = require("./batch.cjs");
4
4
  const computed = require("./computed.cjs");
5
5
  const effect = require("./effect.cjs");
6
+ const is = require("./is.cjs");
6
7
  const signal = require("./signal.cjs");
7
8
  exports.startBatch = batch.startBatch;
8
9
  exports.stopBatch = batch.stopBatch;
9
10
  exports.computed = computed.computed;
10
11
  exports.effect = effect.effect;
12
+ exports.isComputed = is.isComputed;
13
+ exports.isEffect = is.isEffect;
14
+ exports.isSignal = is.isSignal;
11
15
  exports.signal = signal.signal;
package/dist/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import { startBatch, stopBatch } from "./batch.js";
2
2
  import { computed } from "./computed.js";
3
3
  import { effect } from "./effect.js";
4
+ import { isComputed, isEffect, isSignal } from "./is.js";
4
5
  import { signal } from "./signal.js";
5
6
  export {
6
7
  computed,
7
8
  effect,
9
+ isComputed,
10
+ isEffect,
11
+ isSignal,
8
12
  signal,
9
13
  startBatch,
10
14
  stopBatch
package/dist/is.cjs ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ function isComputed(value) {
4
+ return isMora(value, "computed");
5
+ }
6
+ function isEffect(value) {
7
+ return isMora(value, "effect");
8
+ }
9
+ function isMora(value, name) {
10
+ return typeof value === "object" && value != null && "$mora" in value && value.$mora === name;
11
+ }
12
+ function isSignal(value) {
13
+ return isMora(value, "signal");
14
+ }
15
+ exports.isComputed = isComputed;
16
+ exports.isEffect = isEffect;
17
+ exports.isSignal = isSignal;
package/dist/is.js ADDED
@@ -0,0 +1,17 @@
1
+ function isComputed(value) {
2
+ return isMora(value, "computed");
3
+ }
4
+ function isEffect(value) {
5
+ return isMora(value, "effect");
6
+ }
7
+ function isMora(value, name) {
8
+ return typeof value === "object" && value != null && "$mora" in value && value.$mora === name;
9
+ }
10
+ function isSignal(value) {
11
+ return isMora(value, "signal");
12
+ }
13
+ export {
14
+ isComputed,
15
+ isEffect,
16
+ isSignal
17
+ };
package/dist/mora.full.js CHANGED
@@ -1,5 +1,8 @@
1
1
  class Effect {
2
2
  constructor(callback) {
3
+ Object.defineProperty(this, '$mora', {
4
+ value: 'effect',
5
+ });
3
6
  this.state = {
4
7
  callback,
5
8
  };
@@ -23,14 +26,43 @@ function effect(callback) {
23
26
  return new Effect(callback);
24
27
  }
25
28
  let activeEffect;
26
- const dirtyEffects = new Set();
29
+
30
+ /**
31
+ * Is the value a computed signal?
32
+ */
33
+ function isComputed(value) {
34
+ return isMora(value, 'computed');
35
+ }
36
+ /**
37
+ * Is the value an effect?
38
+ */
39
+ function isEffect(value) {
40
+ return isMora(value, 'effect');
41
+ }
42
+ function isMora(value, name) {
43
+ return (typeof value === 'object' &&
44
+ value != null &&
45
+ '$mora' in value &&
46
+ value.$mora === name);
47
+ }
48
+ /**
49
+ * Is the value a signal?
50
+ */
51
+ function isSignal(value) {
52
+ return isMora(value, 'signal');
53
+ }
27
54
 
28
55
  function flushEffects() {
29
- while (batchDepth === 0 && dirtyEffects.size > 0) {
30
- const effects = [...dirtyEffects];
31
- dirtyEffects.clear();
32
- for (const effect of effects) {
33
- runEffect(effect);
56
+ while (batchDepth === 0 && batchedHandlers.size > 0) {
57
+ const handlers = [...batchedHandlers];
58
+ batchedHandlers.clear();
59
+ for (const handler of handlers) {
60
+ if (isEffect(handler)) {
61
+ runEffect(handler);
62
+ }
63
+ else {
64
+ handler.callback(handler.state.value);
65
+ }
34
66
  }
35
67
  }
36
68
  }
@@ -49,17 +81,50 @@ function stopBatch() {
49
81
  }
50
82
  flushEffects();
51
83
  }
84
+ const batchedHandlers = new Set();
52
85
  let batchDepth = 0;
53
86
 
87
+ class Subscription {
88
+ state;
89
+ callback;
90
+ constructor(state, callback) {
91
+ this.state = state;
92
+ this.callback = callback;
93
+ callback(state.value);
94
+ }
95
+ }
96
+ function noop() { }
97
+ function subscribe(state, callback) {
98
+ if (typeof callback !== 'function' || state.subscriptions.has(callback)) {
99
+ return noop;
100
+ }
101
+ state.subscriptions.set(callback, new Subscription(state, callback));
102
+ return () => {
103
+ unsubscribe(state, callback);
104
+ };
105
+ }
106
+ function unsubscribe(state, callback) {
107
+ const subscription = state.subscriptions.get(callback);
108
+ state.subscriptions.delete(callback);
109
+ if (subscription != null) {
110
+ subscription.callback = noop;
111
+ subscription.state = undefined;
112
+ }
113
+ }
114
+
54
115
  let activeComputed;
55
116
  class Computed {
56
117
  state;
57
118
  constructor(callback) {
119
+ Object.defineProperty(this, '$mora', {
120
+ value: 'computed',
121
+ });
58
122
  this.state = {
59
123
  computeds: new Set(),
60
124
  dirty: true,
61
125
  effect: undefined,
62
126
  effects: new Set(),
127
+ subscriptions: new Map(),
63
128
  value: undefined,
64
129
  };
65
130
  this.state.effect = effect(() => {
@@ -74,7 +139,10 @@ class Computed {
74
139
  computed.state.dirty = true;
75
140
  }
76
141
  for (const effect of this.state.effects) {
77
- dirtyEffects.add(effect);
142
+ batchedHandlers.add(effect);
143
+ }
144
+ for (const [, subscription] of this.state.subscriptions) {
145
+ subscription.callback(value);
78
146
  }
79
147
  }
80
148
  this.state.dirty = false;
@@ -102,6 +170,18 @@ class Computed {
102
170
  peek() {
103
171
  return this.state.value;
104
172
  }
173
+ /**
174
+ * Subscribe to changes
175
+ */
176
+ subscribe(callback) {
177
+ return subscribe(this.state, callback);
178
+ }
179
+ /**
180
+ * Unsubscribe from changes
181
+ */
182
+ unsubscribe(callback) {
183
+ unsubscribe(this.state, callback);
184
+ }
105
185
  }
106
186
  /**
107
187
  * Create a computed value
@@ -113,10 +193,14 @@ function computed(callback) {
113
193
  class Signal {
114
194
  state;
115
195
  constructor(value) {
196
+ Object.defineProperty(this, '$mora', {
197
+ value: 'signal',
198
+ });
116
199
  this.state = {
117
200
  value,
118
201
  computeds: new Set(),
119
202
  effects: new Set(),
203
+ subscriptions: new Map(),
120
204
  };
121
205
  }
122
206
  /**
@@ -149,12 +233,27 @@ class Signal {
149
233
  computed.state.dirty = true;
150
234
  }
151
235
  for (const effect of this.state.effects) {
152
- dirtyEffects.add(effect);
236
+ batchedHandlers.add(effect);
237
+ }
238
+ for (const [, subscription] of this.state.subscriptions) {
239
+ batchedHandlers.add(subscription);
153
240
  }
154
241
  if (batchDepth === 0) {
155
242
  flushEffects();
156
243
  }
157
244
  }
245
+ /**
246
+ * Subscribe to changes
247
+ */
248
+ subscribe(callback) {
249
+ return subscribe(this.state, callback);
250
+ }
251
+ /**
252
+ * Unsubscribe from changes
253
+ */
254
+ unsubscribe(callback) {
255
+ unsubscribe(this.state, callback);
256
+ }
158
257
  /**
159
258
  * Update the value
160
259
  */
@@ -166,4 +265,4 @@ function signal(value) {
166
265
  return new Signal(value);
167
266
  }
168
267
 
169
- export { computed, effect, signal, startBatch, stopBatch };
268
+ export { computed, effect, isComputed, isEffect, isSignal, signal, startBatch, stopBatch };
package/dist/signal.cjs CHANGED
@@ -6,13 +6,18 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
6
6
  const batch = require("./batch.cjs");
7
7
  const computed = require("./computed.cjs");
8
8
  const effect = require("./effect.cjs");
9
+ const subscription = require("./subscription.cjs");
9
10
  class Signal {
10
11
  constructor(value) {
11
12
  __publicField(this, "state");
13
+ Object.defineProperty(this, "$mora", {
14
+ value: "signal"
15
+ });
12
16
  this.state = {
13
17
  value,
14
18
  computeds: /* @__PURE__ */ new Set(),
15
- effects: /* @__PURE__ */ new Set()
19
+ effects: /* @__PURE__ */ new Set(),
20
+ subscriptions: /* @__PURE__ */ new Map()
16
21
  };
17
22
  }
18
23
  /**
@@ -44,13 +49,28 @@ class Signal {
44
49
  for (const computed2 of this.state.computeds) {
45
50
  computed2.state.dirty = true;
46
51
  }
47
- for (const effect$1 of this.state.effects) {
48
- effect.dirtyEffects.add(effect$1);
52
+ for (const effect2 of this.state.effects) {
53
+ batch.batchedHandlers.add(effect2);
54
+ }
55
+ for (const [, subscription2] of this.state.subscriptions) {
56
+ batch.batchedHandlers.add(subscription2);
49
57
  }
50
58
  if (batch.batchDepth === 0) {
51
59
  batch.flushEffects();
52
60
  }
53
61
  }
62
+ /**
63
+ * Subscribe to changes
64
+ */
65
+ subscribe(callback) {
66
+ return subscription.subscribe(this.state, callback);
67
+ }
68
+ /**
69
+ * Unsubscribe from changes
70
+ */
71
+ unsubscribe(callback) {
72
+ subscription.unsubscribe(this.state, callback);
73
+ }
54
74
  /**
55
75
  * Update the value
56
76
  */
package/dist/signal.js CHANGED
@@ -1,16 +1,21 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { flushEffects, batchDepth } from "./batch.js";
4
+ import { batchedHandlers, flushEffects, batchDepth } from "./batch.js";
5
5
  import { activeComputed } from "./computed.js";
6
- import { activeEffect, dirtyEffects } from "./effect.js";
6
+ import { activeEffect } from "./effect.js";
7
+ import { subscribe, unsubscribe } from "./subscription.js";
7
8
  class Signal {
8
9
  constructor(value) {
9
10
  __publicField(this, "state");
11
+ Object.defineProperty(this, "$mora", {
12
+ value: "signal"
13
+ });
10
14
  this.state = {
11
15
  value,
12
16
  computeds: /* @__PURE__ */ new Set(),
13
- effects: /* @__PURE__ */ new Set()
17
+ effects: /* @__PURE__ */ new Set(),
18
+ subscriptions: /* @__PURE__ */ new Map()
14
19
  };
15
20
  }
16
21
  /**
@@ -43,12 +48,27 @@ class Signal {
43
48
  computed.state.dirty = true;
44
49
  }
45
50
  for (const effect of this.state.effects) {
46
- dirtyEffects.add(effect);
51
+ batchedHandlers.add(effect);
52
+ }
53
+ for (const [, subscription] of this.state.subscriptions) {
54
+ batchedHandlers.add(subscription);
47
55
  }
48
56
  if (batchDepth === 0) {
49
57
  flushEffects();
50
58
  }
51
59
  }
60
+ /**
61
+ * Subscribe to changes
62
+ */
63
+ subscribe(callback) {
64
+ return subscribe(this.state, callback);
65
+ }
66
+ /**
67
+ * Unsubscribe from changes
68
+ */
69
+ unsubscribe(callback) {
70
+ unsubscribe(this.state, callback);
71
+ }
52
72
  /**
53
73
  * Update the value
54
74
  */
@@ -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
@@ -3,10 +3,12 @@
3
3
  "name": "Oscar Palmér",
4
4
  "url": "https://oscarpalmer.se"
5
5
  },
6
+ "dependencies": {
7
+ "@oscarpalmer/atoms": "^0.100"
8
+ },
6
9
  "description": "Signals and stuff…",
7
10
  "devDependencies": {
8
11
  "@biomejs/biome": "^1.9",
9
- "@oscarpalmer/atoms": "^0.100",
10
12
  "@rollup/plugin-node-resolve": "^16",
11
13
  "@rollup/plugin-typescript": "^12.1",
12
14
  "@types/node": "^22.15",
@@ -52,5 +54,5 @@
52
54
  },
53
55
  "type": "module",
54
56
  "types": "./types/index.d.ts",
55
- "version": "0.8.0"
57
+ "version": "0.10.0"
56
58
  }