@jay-framework/reactive 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.
@@ -0,0 +1,43 @@
1
+ declare enum MeasureOfChange {
2
+ NO_CHANGE = 0,
3
+ PARTIAL = 1,
4
+ FULL = 2
5
+ }
6
+ type Next<T> = (t: T) => T;
7
+ type Setter<T> = (t: T | Next<T>) => T;
8
+ type Getter<T> = () => T;
9
+ type Reaction = (measureOfChange: MeasureOfChange) => void;
10
+ type ValueOrGetter<T> = T | Getter<T>;
11
+ declare const GetterMark: unique symbol;
12
+ declare const SetterMark: unique symbol;
13
+ declare class Reactive {
14
+ private batchedReactionsToRun;
15
+ private isAutoBatchScheduled;
16
+ protected reactionIndex: number;
17
+ private reactions;
18
+ private reactionDependencies;
19
+ private dirty;
20
+ private dirtyResolve;
21
+ private timeout;
22
+ protected inBatchReactions: boolean;
23
+ private inFlush;
24
+ private reactionGlobalKey;
25
+ private reactivesToFlush;
26
+ private disabled;
27
+ private allowedPairedReactives;
28
+ createSignal<T>(value: ValueOrGetter<T>, measureOfChange?: MeasureOfChange): [get: Getter<T>, set: Setter<T>];
29
+ protected triggerReaction(index: number, measureOfChange: MeasureOfChange, paired: boolean): void;
30
+ enablePairing(sourceReactive: Reactive): void;
31
+ createReaction(func: Reaction): void;
32
+ batchReactions<T>(func: () => T): T;
33
+ private ScheduleAutoBatchRuns;
34
+ toBeClean(): Promise<void>;
35
+ private runReaction;
36
+ flush(): void;
37
+ enable(): void;
38
+ disable(): void;
39
+ }
40
+ declare function setMkReactive(mkReactive: (...reactiveNames: (string | number)[]) => Reactive): void;
41
+ declare function mkReactive(...reactiveNames: (string | number)[]): Reactive;
42
+
43
+ export { type Getter, GetterMark, MeasureOfChange, type Next, type Reaction, Reactive, type Setter, SetterMark, type ValueOrGetter, mkReactive, setMkReactive };
package/dist/index.js ADDED
@@ -0,0 +1,209 @@
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) => {
4
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ return value;
6
+ };
7
+ var MeasureOfChange = /* @__PURE__ */ ((MeasureOfChange2) => {
8
+ MeasureOfChange2[MeasureOfChange2["NO_CHANGE"] = 0] = "NO_CHANGE";
9
+ MeasureOfChange2[MeasureOfChange2["PARTIAL"] = 1] = "PARTIAL";
10
+ MeasureOfChange2[MeasureOfChange2["FULL"] = 2] = "FULL";
11
+ return MeasureOfChange2;
12
+ })(MeasureOfChange || {});
13
+ const GetterMark = Symbol.for("getterMark");
14
+ const SetterMark = Symbol.for("setterMark");
15
+ const runningReactions = [];
16
+ function pushRunningReaction(reactiveGlobalKey) {
17
+ runningReactions.push(reactiveGlobalKey);
18
+ }
19
+ function popRunningReaction() {
20
+ runningReactions.pop();
21
+ }
22
+ class Reactive {
23
+ constructor() {
24
+ __publicField(this, "batchedReactionsToRun", []);
25
+ __publicField(this, "isAutoBatchScheduled", false);
26
+ __publicField(this, "reactionIndex", 0);
27
+ __publicField(this, "reactions", []);
28
+ __publicField(this, "reactionDependencies", []);
29
+ __publicField(this, "dirty", Promise.resolve());
30
+ __publicField(this, "dirtyResolve");
31
+ __publicField(this, "timeout");
32
+ __publicField(this, "inBatchReactions");
33
+ __publicField(this, "inFlush");
34
+ __publicField(this, "reactionGlobalKey", []);
35
+ __publicField(this, "reactivesToFlush", /* @__PURE__ */ new Set());
36
+ __publicField(this, "disabled", false);
37
+ __publicField(this, "allowedPairedReactives", /* @__PURE__ */ new WeakSet());
38
+ }
39
+ createSignal(value, measureOfChange = 2) {
40
+ let current;
41
+ const reactionsToRerun = [];
42
+ let pairedReactionsToRun = /* @__PURE__ */ new Set();
43
+ const triggerReactions = () => {
44
+ for (let index = 0; index < reactionsToRerun.length; index++) {
45
+ if (reactionsToRerun[index]) {
46
+ this.triggerReaction(index, measureOfChange, false);
47
+ }
48
+ }
49
+ pairedReactionsToRun.forEach(([reactive, index]) => {
50
+ if (reactive.allowedPairedReactives.has(this)) {
51
+ reactive.triggerReaction(index, measureOfChange, true);
52
+ this.reactivesToFlush.add(reactive);
53
+ }
54
+ });
55
+ };
56
+ const setter = (value2) => {
57
+ let materializedValue = typeof value2 === "function" ? value2(current) : value2;
58
+ let isModified = materializedValue !== current;
59
+ current = materializedValue;
60
+ if (isModified) {
61
+ triggerReactions();
62
+ }
63
+ return current;
64
+ };
65
+ const resetDependency = (reactionGlobalKey) => {
66
+ reactionsToRerun[reactionGlobalKey[1]] = false;
67
+ };
68
+ const resetPairedDependency = (reactionGlobalKey) => {
69
+ pairedReactionsToRun.delete(reactionGlobalKey);
70
+ };
71
+ const getter = () => {
72
+ const runningReactionsLength = runningReactions.length;
73
+ for (let index = runningReactionsLength - 1; index > -1; index--) {
74
+ const [reactive, reactionIndex] = runningReactions[index];
75
+ if (reactive === this) {
76
+ reactionsToRerun[reactionIndex] = true;
77
+ this.reactionDependencies[reactionIndex].add(resetDependency);
78
+ break;
79
+ } else {
80
+ pairedReactionsToRun.add(runningReactions[index]);
81
+ reactive.reactionDependencies[reactionIndex].add(resetPairedDependency);
82
+ }
83
+ }
84
+ return current;
85
+ };
86
+ if (typeof value === "function") {
87
+ this.createReaction(() => {
88
+ let newValue = value();
89
+ setter(newValue);
90
+ });
91
+ } else
92
+ setter(value);
93
+ getter[GetterMark] = true;
94
+ setter[SetterMark] = true;
95
+ return [getter, setter];
96
+ }
97
+ triggerReaction(index, measureOfChange, paired) {
98
+ if (!this.inBatchReactions && !paired)
99
+ this.ScheduleAutoBatchRuns();
100
+ this.batchedReactionsToRun[index] = Math.max(
101
+ measureOfChange,
102
+ this.batchedReactionsToRun[index] || 0
103
+ );
104
+ }
105
+ enablePairing(sourceReactive) {
106
+ this.allowedPairedReactives.add(sourceReactive);
107
+ }
108
+ createReaction(func) {
109
+ let reactionIndex = this.reactionIndex++;
110
+ this.reactions[reactionIndex] = func;
111
+ this.reactionGlobalKey[reactionIndex] = [this, reactionIndex];
112
+ this.reactionDependencies[reactionIndex] = /* @__PURE__ */ new Set();
113
+ this.runReaction(
114
+ reactionIndex,
115
+ 2
116
+ /* FULL */
117
+ );
118
+ }
119
+ batchReactions(func) {
120
+ if (this.inBatchReactions || this.inFlush)
121
+ return func();
122
+ this.inBatchReactions = true;
123
+ [this.dirty, this.dirtyResolve] = mkResolvablePromise();
124
+ try {
125
+ return func();
126
+ } finally {
127
+ this.flush();
128
+ this.inBatchReactions = false;
129
+ this.dirtyResolve();
130
+ }
131
+ }
132
+ ScheduleAutoBatchRuns() {
133
+ if (!this.isAutoBatchScheduled) {
134
+ this.isAutoBatchScheduled = true;
135
+ [this.dirty, this.dirtyResolve] = mkResolvablePromise();
136
+ this.timeout = setTimeout(() => {
137
+ this.timeout = void 0;
138
+ this.flush();
139
+ }, 0);
140
+ }
141
+ }
142
+ toBeClean() {
143
+ return this.dirty;
144
+ }
145
+ runReaction(reactionIndex, measureOfChange) {
146
+ this.reactionDependencies[reactionIndex].forEach(
147
+ (resetDependency) => resetDependency(this.reactionGlobalKey[reactionIndex])
148
+ );
149
+ this.reactionDependencies[reactionIndex].clear();
150
+ pushRunningReaction(this.reactionGlobalKey[reactionIndex]);
151
+ try {
152
+ this.reactions[reactionIndex](measureOfChange);
153
+ } finally {
154
+ popRunningReaction();
155
+ }
156
+ }
157
+ flush() {
158
+ if (this.inFlush || this.disabled)
159
+ return;
160
+ this.inFlush = true;
161
+ try {
162
+ for (let index = 0; index < this.batchedReactionsToRun.length; index++)
163
+ if (this.batchedReactionsToRun[index])
164
+ this.runReaction(index, this.batchedReactionsToRun[index]);
165
+ if (this.isAutoBatchScheduled) {
166
+ this.isAutoBatchScheduled = false;
167
+ if (this.timeout)
168
+ clearTimeout(this.timeout);
169
+ this.timeout = void 0;
170
+ }
171
+ this.batchedReactionsToRun = [];
172
+ this.dirtyResolve && this.dirtyResolve();
173
+ } finally {
174
+ this.inFlush = false;
175
+ this.reactivesToFlush.forEach((reactive) => {
176
+ if (!reactive.inBatchReactions)
177
+ reactive.flush();
178
+ });
179
+ this.reactivesToFlush.clear();
180
+ }
181
+ }
182
+ enable() {
183
+ this.disabled = false;
184
+ this.flush();
185
+ }
186
+ disable() {
187
+ this.disabled = true;
188
+ }
189
+ }
190
+ function mkResolvablePromise() {
191
+ let resolve;
192
+ let promise = new Promise((res) => resolve = res);
193
+ return [promise, resolve];
194
+ }
195
+ let _mkReactive = (...reactiveNames) => new Reactive();
196
+ function setMkReactive(mkReactive2) {
197
+ _mkReactive = mkReactive2;
198
+ }
199
+ function mkReactive(...reactiveNames) {
200
+ return _mkReactive(...reactiveNames);
201
+ }
202
+ export {
203
+ GetterMark,
204
+ MeasureOfChange,
205
+ Reactive,
206
+ SetterMark,
207
+ mkReactive,
208
+ setMkReactive
209
+ };
@@ -0,0 +1,197 @@
1
+ import { Reactive, MeasureOfChange, setMkReactive } from "./index.js";
2
+ class ReactiveTracer {
3
+ constructor(flushToConsole = false) {
4
+ this.flushToConsole = flushToConsole;
5
+ this.log = [];
6
+ this.getStates = [];
7
+ this.setStates = [];
8
+ this.scheduledReactions = [];
9
+ this.inReaction = -1;
10
+ this.batches = [];
11
+ this.reactionLogPosition = [];
12
+ this.ident = "";
13
+ this.settingSignalFromBatch = "";
14
+ }
15
+ logGetState(name) {
16
+ if (this.inReaction > -1)
17
+ this.getStates[this.inReaction].add(name);
18
+ }
19
+ logSetState(name) {
20
+ if (this.inReaction > -1)
21
+ this.setStates[this.inReaction].add(name);
22
+ else {
23
+ this.scheduledReactions[this.inReaction] = /* @__PURE__ */ new Set();
24
+ this.settingSignalFromBatch = name;
25
+ }
26
+ }
27
+ logAfterSetState() {
28
+ if (this.inReaction === -1) {
29
+ const scheduledReactions = [...this.scheduledReactions[this.inReaction]].sort().join(",");
30
+ this.doLog(
31
+ `${this.batches.join(", ")} - batch: -> (${this.settingSignalFromBatch}) --> (${scheduledReactions})`
32
+ );
33
+ }
34
+ }
35
+ beforeReaction() {
36
+ this.inReaction++;
37
+ this.getStates[this.inReaction] = /* @__PURE__ */ new Set();
38
+ this.setStates[this.inReaction] = /* @__PURE__ */ new Set();
39
+ this.scheduledReactions[this.inReaction] = /* @__PURE__ */ new Set();
40
+ this.reactionLogPosition.push(this.log.length);
41
+ this.ident += " ";
42
+ }
43
+ completeReaction(name, reactionName) {
44
+ this.ident = this.ident.slice(0, -2);
45
+ const reactionLogPosition = this.reactionLogPosition.pop();
46
+ const signalGetters = [...this.getStates[this.inReaction]].sort().join(",");
47
+ const signalSetters = [...this.setStates[this.inReaction]].sort().join(",");
48
+ const scheduledReactions = [...this.scheduledReactions[this.inReaction]].sort().join(",");
49
+ const logMessage = `${this.ident}${name} - ${reactionName}: (${signalGetters}) -> (${signalSetters}) --> (${scheduledReactions})`;
50
+ this.log.splice(reactionLogPosition, 0, logMessage);
51
+ this.inReaction--;
52
+ if (this.inReaction === -1)
53
+ this.flushLog();
54
+ }
55
+ beforeBatch(name) {
56
+ this.batches.push(name);
57
+ }
58
+ completeBatch() {
59
+ this.batches.pop();
60
+ }
61
+ flush(name) {
62
+ this.doLog(`${name} - flush!!!`);
63
+ this.ident += " ";
64
+ }
65
+ flushEnd(name) {
66
+ this.ident = this.ident.slice(0, -2);
67
+ this.doLog(`${name} - flush end`);
68
+ }
69
+ logToBeClean(name) {
70
+ this.doLog(`${name} - await toBeClean!!!`);
71
+ }
72
+ triggerReaction(name, index, scheduleAutoBatchRuns) {
73
+ this.scheduledReactions[this.inReaction].add(
74
+ `${name} - ${formatReactionName(index)}${scheduleAutoBatchRuns ? " async" : ""}`
75
+ );
76
+ }
77
+ createSignal(name, stateName) {
78
+ this.doLog(`${name} - createSignal ${stateName}`);
79
+ }
80
+ doLog(message) {
81
+ this.log.push(`${this.ident}${message}`);
82
+ if (this.inReaction === -1)
83
+ this.flushLog();
84
+ }
85
+ flushLog() {
86
+ if (this.flushToConsole) {
87
+ this.log.forEach((entry) => console.debug(entry));
88
+ this.log = [];
89
+ }
90
+ }
91
+ }
92
+ const romanNumerals = {
93
+ M: 1e3,
94
+ CM: 900,
95
+ D: 500,
96
+ CD: 400,
97
+ C: 100,
98
+ XC: 90,
99
+ L: 50,
100
+ XL: 40,
101
+ X: 10,
102
+ IX: 9,
103
+ V: 5,
104
+ IV: 4,
105
+ I: 1
106
+ };
107
+ function toRoman(num) {
108
+ let roman = "";
109
+ for (let i in romanNumerals) {
110
+ while (num >= romanNumerals[i]) {
111
+ roman += i;
112
+ num -= romanNumerals[i];
113
+ }
114
+ }
115
+ return roman;
116
+ }
117
+ function formatReactionName(num) {
118
+ return toRoman(num + 1);
119
+ }
120
+ class ReactiveWithTracking extends Reactive {
121
+ constructor(name, reactiveTracer) {
122
+ super();
123
+ this.name = name;
124
+ this.reactiveTracer = reactiveTracer;
125
+ this.stateIndex = 1;
126
+ }
127
+ createSignal(value, measureOfChange = MeasureOfChange.FULL) {
128
+ const stateName = this.name + this.stateIndex++;
129
+ this.reactiveTracer.createSignal(this.name, stateName);
130
+ const [getter, setter] = super.createSignal(value, measureOfChange);
131
+ const loggedSetter = (value2) => {
132
+ this.reactiveTracer.logSetState(stateName);
133
+ const ret = setter(value2);
134
+ this.reactiveTracer.logAfterSetState();
135
+ return ret;
136
+ };
137
+ const loggedGetter = () => {
138
+ this.reactiveTracer.logGetState(stateName);
139
+ return getter();
140
+ };
141
+ return [loggedGetter, loggedSetter];
142
+ }
143
+ createReaction(func) {
144
+ const reactionName = formatReactionName(this.reactionIndex);
145
+ super.createReaction((measureOfChange) => {
146
+ this.reactiveTracer.beforeReaction();
147
+ try {
148
+ func(measureOfChange);
149
+ } finally {
150
+ this.reactiveTracer.completeReaction(this.name, reactionName);
151
+ }
152
+ });
153
+ }
154
+ triggerReaction(index, measureOfChange, paired) {
155
+ this.reactiveTracer.triggerReaction(this.name, index, !this.inBatchReactions && !paired);
156
+ super.triggerReaction(index, measureOfChange, paired);
157
+ }
158
+ batchReactions(func) {
159
+ this.reactiveTracer.beforeBatch(this.name);
160
+ try {
161
+ return super.batchReactions(func);
162
+ } finally {
163
+ this.reactiveTracer.completeBatch();
164
+ }
165
+ }
166
+ flush() {
167
+ this.reactiveTracer.flush(this.name);
168
+ super.flush();
169
+ this.reactiveTracer.flushEnd(this.name);
170
+ }
171
+ toBeClean() {
172
+ this.reactiveTracer.logToBeClean(this.name);
173
+ return super.toBeClean();
174
+ }
175
+ }
176
+ const globalReactiveTracer = new ReactiveTracer(true);
177
+ let runningNumber = 1;
178
+ function numberToAlphaNumeric(num) {
179
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
180
+ let result = "";
181
+ while (num > 0) {
182
+ num--;
183
+ result = alphabet[num % 26] + result;
184
+ num = Math.floor(num / 26);
185
+ }
186
+ return result || "A";
187
+ }
188
+ setMkReactive((...reactiveNames) => {
189
+ return new ReactiveWithTracking(
190
+ [numberToAlphaNumeric(runningNumber++), ...reactiveNames].join("-"),
191
+ globalReactiveTracer
192
+ );
193
+ });
194
+ export {
195
+ ReactiveTracer,
196
+ ReactiveWithTracking
197
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@jay-framework/reactive",
3
+ "version": "0.5.0",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "main": "dist/index.js",
7
+ "exports": {
8
+ ".": "./dist/index.js",
9
+ "./tracing": "./dist/tracing.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "readme.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "npm run build:js && npm run build:types",
17
+ "build:watch": "npm run build:js -- --watch & npm run build:types -- --watch",
18
+ "build:js": "vite build",
19
+ "build:types": "tsup lib/index.ts --dts-only --format esm",
20
+ "build:check-types": "tsc",
21
+ "clean": "rimraf dist",
22
+ "confirm": "npm run clean && npm run build && npm run build:check-types && npm run test",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "devDependencies": {
27
+ "@jay-framework/dev-environment": "workspace:^",
28
+ "@types/node": "^20.11.5",
29
+ "rimraf": "^5.0.5",
30
+ "tsup": "^8.0.1",
31
+ "typescript": "^5.3.3",
32
+ "vite": "^5.0.11",
33
+ "vitest": "^1.2.1"
34
+ }
35
+ }
package/readme.md ADDED
@@ -0,0 +1,284 @@
1
+ # Jay Reactive Module
2
+
3
+ The Reactive module is a minimal reactive core implementation that handles storing data,
4
+ reacting to data change and detecting if data has actually changed.
5
+
6
+ Reactive will strive to run reactions as a batch and will do so **sync** when using `batchReactions` or
7
+ **async** if `batchReactions` was not used. When there are pending reactions to be run async, `toBeClean`
8
+ can be used to wait for the reactions to run using `await reactive.toBeClean()`.
9
+
10
+ The package one class - the `Reactive` which is a simple reactive core, at which reactions are dependent on signals.
11
+ When a signal is updated, any of the dependent reactions are re-run.
12
+
13
+ The reactions auto track which signals they depend on. On each run of a reaction,
14
+ it will recalculate dependencies to ensure it only depends on signal values that are actually in use.
15
+ A direct impact is that conditions based on signals are supported in reactions, and the reaction rerun will take
16
+ into account the conditions.
17
+
18
+ Reactive can also pair, creating dependencies between multiple Reactive instances. See the section below on Reactive Pairing
19
+
20
+ ## Notes:
21
+
22
+ - `Reactive` is intended to be an internal core implementation for state management and not a user facing API.
23
+ - `Reactive` is used by `@jay-framework/component` as state management for components, at which each component has it's own independent
24
+ instance of `Reactive`.
25
+ - `@jay-framework/component` also defines reactive context which is also using an independent `Reactive` instance.
26
+ - one `Reactive` can depend on a signal from another `Reactive` creating `Reactive` pairing discussed below.
27
+ - `@jay-framework/reactive` is inspired by [solid.js](https://www.solidjs.com/) state management (amazing framework, BTW).
28
+ - `Reactive.enable` and `Reactive.disable` are used by `@jay-framework/component` to disable and enable reactive as a component unmounts and mounts.
29
+
30
+ ## createSignal
31
+
32
+ ```typescript
33
+ type Next<T> = (t: T) => T;
34
+ type Setter<T> = (t: T | Next<T>) => T;
35
+ type Getter<T> = () => T;
36
+ type ValueOrGetter<T> = T | Getter<T>;
37
+ declare function createSignal<T>(
38
+ value: ValueOrGetter<T>,
39
+ measureOfChange: MeasureOfChange = MeasureOfChange.FULL,
40
+ ): [get: Getter<T>, set: Setter<T>];
41
+ ```
42
+
43
+ Creates a signal getter / setter pair such that when setting signal value, any dependent reaction is rerun.
44
+ The reactions run on `setTimeout(...,0)`, or at the end of a batch when using `batchReactions`.
45
+
46
+ The getter always returns the signal value
47
+ The setter accepts a new value or a function to compute the next value, as well as a `MeasureOfChange`.
48
+
49
+ ```typescript
50
+ const [getter, setter] = reactive.createSignal(12);
51
+
52
+ getter(); // returns 12
53
+ setter(13);
54
+ setter((x) => x + 1);
55
+
56
+ const [getter2, setter2] = reactive.createSignal(() => `signal value is ${getter()}`);
57
+ ```
58
+
59
+ ### createSignal parameters
60
+
61
+ - `value: ValueOrGetter<T>` - an initial value for the signal, or a getter function to track using `createReaction`.
62
+ - `measureOfChange: MeasureOfChange = MeasureOfChange.FULL` - an indicator of how large a change is signal is considered
63
+ within reactions that depend on this signal (when a reaction is run, it also gets the `max(...measureOfChange)`
64
+ of all signals that have changed and it depends on).
65
+
66
+ ### getter
67
+
68
+ the first function returned by `createSignal` is the `getter` function which returns the current value of the signal.
69
+
70
+ ### setter
71
+
72
+ The second function returned is `setter` which accepts one parameter - a new value for the signal,
73
+ or a function to update the signal value. Note that a change is defined by strict equality - using the `===` and `!==` operators.
74
+
75
+ ## createReaction
76
+
77
+ ```typescript
78
+ export type Reaction = (measureOfChange: MeasureOfChange) => void;
79
+ reactive.createReaction(func: Reaction);
80
+ ```
81
+
82
+ Creates a reaction that re-runs when signals it depends on changes.
83
+ It will re-run on `setTimeout(..., 0)`, or at the end of a batch when using `batchReactions`.
84
+ The `Reaction` accepts a `MeasureOfChange` computed as the `max(...measureOfChange)`
85
+ of all signals that have changed and the reaction depends on.
86
+
87
+ The `Reaction` function is running once as part of the call to `createReaction` used to figure out what
88
+ initial dependencies to track. On each run of the `Reaction` function dependencies are recomputed and the
89
+ function will only rerun with relevant dependencies are updated.
90
+
91
+ ```typescript
92
+ reactive.createReaction(() => {
93
+ console.log(signalGetter());
94
+ });
95
+ ```
96
+
97
+ Note that only dependencies (signal getters) that are actually in use are set as dependencies.
98
+ In the following case, the reaction will track signals `a` and `b`, but will not track signal `c` (by design).
99
+
100
+ ```typescript
101
+ const [a, setA] = reactive.createSignal(true);
102
+ const [b, setB] = reactive.createSignal('abc');
103
+ const [c, setC] = reactive.createSignal('def');
104
+
105
+ reactive.createReaction(() => {
106
+ if (a()) b();
107
+ else c();
108
+ });
109
+ ```
110
+
111
+ Once `a` or `b` update, the reaction will rerun.
112
+
113
+ If `a` is set to false, the reaction will now depend on `a` and `c`.
114
+
115
+ ## batchReactions
116
+
117
+ ```typescript
118
+ reactive.batchReactions(func: () => void);
119
+ ```
120
+
121
+ Batch reaction enables to update multiple signals while computing reactions only once. It is important for
122
+ performance optimizations, to enable rendering DOM updates once when a component updates multiple signals. It
123
+ is built for the component API to optimize rendering.
124
+
125
+ ```typescript
126
+ let reactive = new Reactive((reactive) => {
127
+ const [a, setA] = reactive.createSignal(false);
128
+ const [b, setB] = reactive.createSignal('abc');
129
+ const [c, setC] = reactive.createSignal('def');
130
+ reactive.createReaction(() => {
131
+ console.log(a(), b(), c());
132
+ });
133
+ });
134
+ // will print the console log false abc def
135
+
136
+ reactive.batchReactions(() => {
137
+ setA(true);
138
+ setB('abcde');
139
+ setC('fghij');
140
+ });
141
+ // will print the console log true abcde fghij
142
+ ```
143
+
144
+ ## toBeClean
145
+
146
+ ```typescript
147
+ reactive.toBeClean(): Promise<void>;
148
+ ```
149
+
150
+ returns a promise that is resolved when pending reactions have run. If there are no pending reactions, the promise
151
+ will resolve immediately.
152
+
153
+ ```typescript
154
+ setA(12);
155
+ setB('Joe');
156
+ // waits for reaction to run
157
+ await reactive.toBeClean();
158
+ ```
159
+
160
+ ## flush
161
+
162
+ ```typescript
163
+ reactive.flush(): void;
164
+ ```
165
+
166
+ In the case of not using batch reactions, reactive will auto batch the reactions and run them async.
167
+ `flush` can be used to force the reactions to run sync.
168
+
169
+ ```typescript
170
+ setA(12);
171
+ setB('Joe');
172
+ // forces reactions to run synchronosly
173
+ reactive.flush();
174
+ ```
175
+
176
+ ## enable & disable
177
+
178
+ ```typescript
179
+ reactive.enable();
180
+ reactive.disable();
181
+ ```
182
+
183
+ Enables and disables the reactive.
184
+ A Disabled reactive will not run reactions.
185
+
186
+ - When calling enable, the reactive will also flush any pending reactions.
187
+ - Reactive are created, by default, enabled.
188
+
189
+ ## MeasureOfChange
190
+
191
+ Measure of Change is an optional value passed when creating signals, which is then used to tune how reactions run.
192
+ The `MeasureOfChange` is defined as an ordered enum, at which case the reaction always gets the max `MeasureOfChange`
193
+ from signals that are updated.
194
+
195
+ It is defined as
196
+
197
+ ```typescript
198
+ export enum MeasureOfChange {
199
+ NO_CHANGE,
200
+ PARTIAL,
201
+ FULL,
202
+ }
203
+ ```
204
+
205
+ At which
206
+
207
+ - `NO_CHANGE` - allows to update a signal without triggering reactions
208
+ - `PARTIAL` - triggers reactions with the `PARTIAL` measure of change, unless other signals are updated with a higher measure of change
209
+ - `FULL` - triggers reactions with the `FULL` measure of change
210
+
211
+ see the `@jay-framework/component` library, the `createDerivedArray` function for an example use case.
212
+
213
+ ## enablePairing
214
+
215
+ Reactive Pairing is useful when an application has multiple reactive instances who need to sync flush between them.
216
+ For instance, with Jay, a context is one reactive and component is another instance of a reactive.
217
+
218
+ Pairing is created explicitly using the `enablePairing` API,
219
+ then by reading a signal value of reactive `A` from a reaction of reactive `B`.
220
+ When paired, once reactive `A` flushes, it will also trigger a flush of reactive `B` after `A` flush completes,
221
+ which will re-run the reaction in `B` that have read a signal value of `A`.
222
+
223
+ ```typescript
224
+ reactive.enablePairing(anotherReactive);
225
+ ```
226
+
227
+ - `reactive` the reactive from which `anotherReactive` signal values are read. In Jay, a component. The `B` above.
228
+ - `anotherReactive` the reactive from which signal values are read. In Jay, a context. The `A` above.
229
+
230
+ example:
231
+
232
+ ```typescript
233
+ B.enablePairing(A);
234
+ ```
235
+
236
+ # Reactive Tracing
237
+
238
+ The reactive library includes the facility to trace how Reactive signals and reactions are running.
239
+
240
+ To enable reactive tracing, import the `@jay-framework/reactive/tracing` module before starting jay.
241
+
242
+ ```typescript
243
+ import '@jay-framework/reactive/tracing';
244
+ ```
245
+
246
+ Reactive tracing outputs tracing similar to the following:
247
+
248
+ ```
249
+ // on counter example creation
250
+ A - createSignal A1
251
+ A - createSignal A2
252
+ A - createSignal A3
253
+ A - I: (A3) -> () --> ()
254
+ A - II: () -> () --> ()
255
+ A - flush!!!
256
+ A - flush end
257
+ A - batch: -> (A1) --> ()
258
+ A - flush!!!
259
+ A - flush end
260
+ A - batch: -> (A3) --> (A - I)
261
+ A - flush!!!
262
+ A - I: (A3) -> () --> ()
263
+ A - flush end
264
+
265
+ // on counter click on a button
266
+ A - flush!!!
267
+ A - flush end
268
+ A - batch: -> (A3) --> (A - I)
269
+ A - flush!!!
270
+ A - I: (A3) -> () --> ()
271
+ A - flush end
272
+ ```
273
+
274
+ The trace should be read as:
275
+
276
+ - `A` - each Reactive gets a letter as a name, like `A`, `B`, `C`, etc.
277
+ - `A - createSignal A1` - creating the first signal.
278
+ - Signals are named after the reactive name + a serial number, like `A1`, `A2`, `A3`, `B1`, etc.
279
+ - `A - I: (A3) -> () --> ()` - running a reaction, including on reaction creation.
280
+ - Reactions are named after the reaction name + serial roman number, like `A - I`, `A - II`, `A - III`, etc.
281
+ - The first `()` are the signals read (using getters) in the reaction.
282
+ - The second `()` are signals written (using setters) in the reaction.
283
+ - The third `()` are reactions to run once this reaction is running.
284
+ - `A - batch: -> (A3) --> (A - I)` - a batch reaction setting the `A3` signal and scheduling the `A - I` reaction to run.