Finished
55
66export class InputCell {
77 constructor(value) {
8 throw new Error('Remove this line and implement the function');
8 this._value = value;
9 this._observers = [];
10 }
11
12 get value() {
13 return this._value;
14 }
15
16 getValue() {
17 return this._value;
918 }
1019
1120 setValue(value) {
12 throw new Error('Remove this line and implement the function');
21 // Edge Case: No change in value, no need to propagate
22 if (this._value === value) {
23 return;
24 }
25
26 this._value = value;
27 this._propagateChanges();
28 }
29
30 _propagateChanges() {
31 // Get all affected cells in topological order
32 const affectedCells = this._getAffectedCells();
33
34 // Store old values before computing new ones
35 const oldValues = new Map();
36 for (const cell of affectedCells) {
37 oldValues.set(cell, cell._value);
38 }
39
40 // Compute new values (reverse order for proper dependency resolution)
41 for (let i = affectedCells.length - 1; i >= 0; i--) {
42 const cell = affectedCells[i];
43 if (cell._computeValueWithoutCallbacks) {
44 cell._computeValueWithoutCallbacks();
45 }
46 }
47
48 // Trigger callbacks for cells that actually changed
49 for (const cell of affectedCells) {
50 if (oldValues.get(cell) !== cell._value) {
51 if (cell._triggerCallbacks) {
52 cell._triggerCallbacks();
53 }
54 }
55 }
56 }
57
58 _getAffectedCells() {
59 const visited = new Set();
60 const result = [];
61
62 const dfs = (cell) => {
63 if (visited.has(cell)) return;
64 visited.add(cell);
65
66 // Visit observers first (post-order traversal)
67 for (const observer of cell._observers) {
68 dfs(observer);
69 }
70
71 // Add current cell last (after all its dependents)
72 result.push(cell);
73 };
74
75 dfs(this);
76 return result;
77 }
78
79 _addObserver(observer) {
80 this._observers.push(observer);
81 }
82
83 _removeObserver(observer) {
84 const index = this._observers.indexOf(observer);
85 // Edge Case: Observer not found
86 if (index !== -1) {
87 this._observers.splice(index, 1);
88 }
1389 }
1490}
1591
1692export class ComputeCell {
1793 constructor(inputCells, fn) {
18 throw new Error('Remove this line and implement the function');
94 this._inputCells = inputCells;
95 this._fn = fn;
96 this._value = undefined;
97 this._callbacks = [];
98 this._observers = [];
99 this._computing = false;
100
101 // Edge Case: Initialize with computed value
102 this._computeValue();
103
104 // Register as observer for all input cells
105 for (const cell of this._inputCells) {
106 cell._addObserver(this);
107 }
108 }
109
110 get value() {
111 return this._value;
112 }
113
114 getValue() {
115 return this._value;
116 }
117
118 _computeValue() {
119 // Edge Case: Prevent infinite recursion in case of circular dependencies
120 if (this._computing) {
121 return;
122 }
123
124 this._computing = true;
125
126 try {
127 const newValue = this._fn(this._inputCells);
128
129 // Edge Case: Only trigger callbacks if value actually changed
130 if (this._value !== newValue) {
131 const oldValue = this._value;
132 this._value = newValue;
133
134 // Only propagate if not already in a propagation cycle
135 if (!this._inPropagation) {
136 this._propagateChanges();
137 }
138 } else {
139 this._value = newValue;
140 }
141 } finally {
142 this._computing = false;
143 }
144 }
145
146 _propagateChanges() {
147 // Prevent nested propagation
148 if (this._inPropagation) {
149 return;
150 }
151
152 this._inPropagation = true;
153
154 try {
155 // Get all affected cells in topological order
156 const affectedCells = this._getAffectedCells();
157
158 // Store old values before computing new ones
159 const oldValues = new Map();
160 for (const cell of affectedCells) {
161 oldValues.set(cell, cell._value);
162 }
163
164 // Set propagation flag on all affected cells
165 for (const cell of affectedCells) {
166 cell._inPropagation = true;
167 }
168
169 // Compute new values
170 for (const cell of affectedCells) {
171 if (cell._computeValueWithoutCallbacks) {
172 cell._computeValueWithoutCallbacks();
173 }
174 }
175
176 // Trigger callbacks for cells that actually changed
177 for (const cell of affectedCells) {
178 if (oldValues.get(cell) !== cell._value) {
179 if (cell._triggerCallbacks) {
180 cell._triggerCallbacks();
181 }
182 }
183 }
184
185 // Trigger callbacks for this cell if it changed
186 if (this._triggerCallbacks) {
187 this._triggerCallbacks();
188 }
189
190 // Clear propagation flag from all affected cells
191 for (const cell of affectedCells) {
192 cell._inPropagation = false;
193 }
194 } finally {
195 this._inPropagation = false;
196 }
197 }
198
199 _computeValueWithoutCallbacks() {
200 // Edge Case: Prevent infinite recursion in case of circular dependencies
201 if (this._computing) {
202 return;
203 }
204
205 this._computing = true;
206
207 try {
208 const newValue = this._fn(this._inputCells);
209 this._value = newValue;
210 } finally {
211 this._computing = false;
212 }
213 }
214
215 _triggerCallbacks() {
216 for (const callback of this._callbacks) {
217 callback._call(this);
218 }
19219 }
20220
21221 addCallback(cb) {
22 throw new Error('Remove this line and implement the function');
222 this._callbacks.push(cb);
23223 }
24224
25225 removeCallback(cb) {
26 throw new Error('Remove this line and implement the function');
226 const index = this._callbacks.indexOf(cb);
227 // Edge Case: Callback not found
228 if (index !== -1) {
229 this._callbacks.splice(index, 1);
230 }
231 }
232
233 _addObserver(observer) {
234 this._observers.push(observer);
235 }
236
237 _removeObserver(observer) {
238 const index = this._observers.indexOf(observer);
239 // Edge Case: Observer not found
240 if (index !== -1) {
241 this._observers.splice(index, 1);
242 }
243 }
244
245
246
247 _getAffectedCells() {
248 const visited = new Set();
249 const result = [];
250
251 const dfs = (cell) => {
252 if (visited.has(cell)) return;
253 visited.add(cell);
254
255 // Visit observers first (post-order traversal)
256 for (const observer of cell._observers) {
257 dfs(observer);
258 }
259
260 // Add current cell last (after all its dependents)
261 result.push(cell);
262 };
263
264 dfs(this);
265 return result;
27266 }
28267}
29268
30269export class CallbackCell {
31270 constructor(fn) {
32 throw new Error('Remove this line and implement the function');
271 this._fn = fn;
272 this._values = [];
273 }
274
275 _call(cell) {
276 const result = this._fn(cell);
277 this._values.push(result);
278 }
279
280 get values() {
281 return [...this._values];
282 }
283
284 getValues() {
285 const result = [...this._values];
286 this._values = [];
287 return result;
33288 }
34289}
Test NameStatus
accepts input
Pass
allows input cell value to be set
Pass
allows setting compute cells
Pass
compute cell takes inputs in correct order
Pass
compute cells update value when inputs are changed
Pass
compute cells can depend on other compute cells
Pass
compute cells fire callbacks
Pass
callbacks fire only when output values change
Pass
static callbacks fire even if their own value has not changed
Pass
callbacks can be added and removed
Pass
removing a callback multiple times doesn't interfere with other callbacks
Pass
callbacks should only be called once, even if multiple dependencies change
Pass
callbacks should not be called if dependencies change but output value doesn't change
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.