@adbl/cells 0.0.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,4 @@
1
+ {
2
+ "cSpell.words": ["wvalue"],
3
+ "deno.enable": false
4
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Sefunmi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # @adbl/cells
2
+
3
+ [![npm version](https://badge.fury.io/js/%40adbl%2Fcells.svg)](https://badge.fury.io/js/%40adbl%2Fcells)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Cells is a powerful yet lightweight library for reactive state management in JavaScript applications. It offers an intuitive API that simplifies the complexities of managing and propagating state changes throughout your application.
7
+
8
+ ## Features
9
+
10
+ - **Simple API**: Easy to learn and use, even for developers new to reactive programming.
11
+ - **Lightweight**: No external dependencies, keeping your project lean.
12
+ - **Flexible**: Works seamlessly with any JavaScript framework or vanilla JS.
13
+ - **Type-safe**: Built with TypeScript, providing excellent type inference and checking.
14
+ - **Performant**: Optimized for efficiency, with features like batched updates to minimize unnecessary computations.
15
+
16
+ ## Installation
17
+
18
+ Get started with Cells in your project:
19
+
20
+ ```bash
21
+ npm install @adbl/cells
22
+ ```
23
+
24
+ Or if you prefer Yarn:
25
+
26
+ ```bash
27
+ yarn add @adbl/cells
28
+ ```
29
+
30
+ ## Core Concepts
31
+
32
+ ### 1. Source Cells
33
+
34
+ Source cells are the building blocks of your reactive state. They hold values that can change over time, automatically notifying dependents when updates occur.
35
+
36
+ ```javascript
37
+ import { Cell } from '@adbl/cells';
38
+
39
+ const count = Cell.source(0);
40
+ console.log(count.value); // Output: 0
41
+
42
+ count.value = 5;
43
+ console.log(count.value); // Output: 5
44
+ ```
45
+
46
+ ### 2. Derived Cells
47
+
48
+ Derived cells allow you to create computed values based on other cells. They update automatically when their dependencies change, ensuring your derived state is always in sync.
49
+
50
+ ```javascript
51
+ const count = Cell.source(0);
52
+ const doubledCount = Cell.derived(() => count.value * 2);
53
+
54
+ console.log(doubledCount.value); // Output: 0
55
+
56
+ count.value = 5;
57
+ console.log(doubledCount.value); // Output: 10
58
+ ```
59
+
60
+ ### 3. Reactive Effects
61
+
62
+ Easily set up listeners to react to changes in cell values, allowing you to create side effects or update your UI in response to state changes.
63
+
64
+ ```javascript
65
+ const count = Cell.source(0);
66
+
67
+ count.listen((newValue) => {
68
+ console.log(`Count changed to: ${newValue}`);
69
+ });
70
+
71
+ count.value = 3; // Output: "Count changed to: 3"
72
+ count.value = 7; // Output: "Count changed to: 7"
73
+ ```
74
+
75
+ ### 4. Global Effects
76
+
77
+ Cells allows you to set up global effects that run before or after any cell is updated, giving you fine-grained control over your application's reactive behavior.
78
+
79
+ ```javascript
80
+ Cell.beforeUpdate((value) => {
81
+ console.log(`About to update a cell with value: ${value}`);
82
+ });
83
+
84
+ Cell.afterUpdate((value) => {
85
+ console.log(`Just updated a cell with value: ${value}`);
86
+ });
87
+ ```
88
+
89
+ ### 5. Batch Updates
90
+
91
+ When you need to perform multiple updates but only want to trigger effects once, you can use batch updates to optimize performance:
92
+
93
+ ```javascript
94
+ const cell1 = Cell.source(0);
95
+ const cell2 = Cell.source(0);
96
+
97
+ Cell.afterUpdate(() => {
98
+ console.log('Update occurred');
99
+ });
100
+
101
+ Cell.batch(() => {
102
+ cell1.value = 1;
103
+ cell2.value = 2;
104
+ });
105
+ // Output: "Update occurred" (only once)
106
+ ```
107
+
108
+ ### 6. Async Operations
109
+
110
+ Cells provides utilities for handling asynchronous operations, making it easy to manage loading states, data, and errors:
111
+
112
+ ```javascript
113
+ const fetchUser = Cell.async(async (userId) => {
114
+ const response = await fetch(`https://api.example.com/users/${userId}`);
115
+ return response.json();
116
+ });
117
+
118
+ const { pending, data, error, run } = fetchUser;
119
+
120
+ pending.listen((isPending) => {
121
+ console.log(isPending ? 'Loading...' : 'Done!');
122
+ });
123
+
124
+ data.listen((userData) => {
125
+ if (userData) {
126
+ console.log('User data:', userData);
127
+ }
128
+ });
129
+
130
+ run(123); // Triggers the async operation
131
+ ```
132
+
133
+ ### 7. Flattening
134
+
135
+ Cells offers utility functions to work with nested cell structures, making it easier to handle complex state shapes:
136
+
137
+ ```javascript
138
+ const nestedCell = Cell.source(Cell.source(5));
139
+ const flattenedValue = Cell.flatten(nestedCell);
140
+ console.log(flattenedValue); // Output: 5
141
+
142
+ const arrayOfCells = [Cell.source(1), Cell.source(2), Cell.source(3)];
143
+ const flattenedArray = Cell.flattenArray(arrayOfCells);
144
+ console.log(flattenedArray); // Output: [1, 2, 3]
145
+
146
+ const objectWithCells = { a: Cell.source(1), b: Cell.source(2) };
147
+ const flattenedObject = Cell.flattenObject(objectWithCells);
148
+ console.log(flattenedObject); // Output: { a: 1, b: 2 }
149
+ ```
150
+
151
+ ### 8. Custom Equality Checks
152
+
153
+ For more complex objects, you can provide custom equality functions to determine when a cell's value has truly changed:
154
+
155
+ ```javascript
156
+ const userCell = Cell.source(
157
+ { name: 'Alice', age: 30 },
158
+ {
159
+ equals: (a, b) => a.name === b.name && a.age === b.age,
160
+ }
161
+ );
162
+ ```
163
+
164
+ ### 9. Named Effects
165
+
166
+ To aid in debugging, you can name your effects, making it easier to track and manage them:
167
+
168
+ ```javascript
169
+ const count = Cell.source(0);
170
+
171
+ count.listen((value) => console.log(`Count is now: ${value}`), {
172
+ name: 'countLogger',
173
+ });
174
+
175
+ console.log(count.isListeningTo('countLogger')); // Output: true
176
+
177
+ count.stopListeningTo('countLogger');
178
+ ```
179
+
180
+ ## Advanced Features and API Details
181
+
182
+ ### Cell Options
183
+
184
+ When creating a source cell, you have fine-grained control over its behavior:
185
+
186
+ ```javascript
187
+ const cell = Cell.source(initialValue, {
188
+ immutable: boolean, // If true, the cell will not allow updates
189
+ shallowProxied: boolean, // If true, only top-level properties are proxied
190
+ equals: (oldValue, newValue) => boolean, // Custom equality function
191
+ });
192
+ ```
193
+
194
+ ### Effect Options
195
+
196
+ When setting up listeners or effects, you can customize their behavior:
197
+
198
+ ```javascript
199
+ cell.listen(callback, {
200
+ once: boolean, // If true, the effect will only run once
201
+ signal: AbortSignal, // An AbortSignal to cancel the effect
202
+ name: string, // A name for the effect (useful for debugging)
203
+ priority: number, // The priority of the effect (higher priority effects run first)
204
+ });
205
+ ```
package/bun.lockb ADDED
Binary file
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ /// <reference types="./types/index.d.ts" />
2
+
3
+ export * from './library/index.js';
package/jsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "noUnusedLocals": true,
7
+ "noUnusedParameters": true,
8
+ "noImplicitAny": true,
9
+ "allowUnreachableCode": false,
10
+ "allowJs": true,
11
+
12
+ "noImplicitReturns": true,
13
+
14
+ "noEmit": false,
15
+ "emitDeclarationOnly": true,
16
+ "declaration": true,
17
+
18
+ "checkJs": true,
19
+ "strict": true,
20
+
21
+ "outDir": "types"
22
+ },
23
+ "include": ["library/**/*", "index.js"],
24
+ "exclude": ["types/**/*", "tests/**/*"]
25
+ }
@@ -0,0 +1,645 @@
1
+ /**
2
+ * @template Input, Output
3
+ * @typedef {Object} AsyncRequestAtoms
4
+ *
5
+ * @property {SourceCell<boolean>} pending
6
+ * Represents the loading state of an asynchronous request.
7
+ *
8
+ * @property {SourceCell<Output|null>} data
9
+ * Represents the data returned by the asynchronous request.
10
+ *
11
+ * @property {SourceCell<Error | null>} error
12
+ * Represents the errors returned by the asynchronous request, if any.
13
+ *
14
+ * @property {NeverIfAny<Input> extends never ? (input?: Input) => Promise<void> : (input: Input) => Promise<void>} run
15
+ * Triggers the asynchronous request.
16
+ *
17
+ * @property {(newInput?: Input, changeLoadingState?: boolean) => Promise<void>} reload Triggers the asynchronous request again with an optional new input and optionally changes the loading state.
18
+ */
19
+
20
+ /**
21
+ * @typedef {object} EffectOptions
22
+ * @property {boolean} [once]
23
+ * Whether the effect should be removed after the first run.
24
+ * @property {AbortSignal} [signal]
25
+ * An AbortSignal to be used to ignore the effect if it is aborted.
26
+ * @property {string} [name]
27
+ * The name of the effect for debugging purposes.
28
+ * @property {number} [priority]
29
+ * The priority of the effect. Higher priority effects are executed first. The default priority is 0.
30
+ */
31
+
32
+ /**
33
+ * @template T
34
+ * @typedef {object} CellOptions
35
+ * @property {boolean} [immutable]
36
+ * Whether the cell should be immutable. If set to true, the cell will not allow updates and will throw an error if the value is changed.
37
+ * @property {boolean} [shallowProxied]
38
+ * Whether the cell's value should be shallowly proxied. If set to true, the cell will only proxy the top-level properties of the value, preventing any changes to nested properties. This can be useful for performance optimizations.
39
+ * @property {(oldValue: T, newValue: T) => boolean} [equals]
40
+ * A function that determines whether two values are equal. If not provided, the default equality function will be used.
41
+ */
42
+
43
+ /**
44
+ * @template T
45
+ * @typedef {0 extends (1 & T) ? never : T} NeverIfAny
46
+ */
47
+
48
+ import { activeComputedValues, root } from './root.js';
49
+
50
+ /**
51
+ * @template T
52
+ */
53
+ export class Cell {
54
+ /**
55
+ * @type {Array<({
56
+ * effect: (newValue: T) => void,
57
+ * options?: EffectOptions,
58
+ * })>}
59
+ * @protected
60
+ */
61
+ effects = [];
62
+
63
+ /**
64
+ * @type {Array<WeakRef<DerivedCell<any>>>}
65
+ * @protected
66
+ */
67
+ derivedCells = [];
68
+
69
+ /**
70
+ * @protected @type T
71
+ */
72
+ wvalue = /** @type {T} */ (null);
73
+
74
+ /**
75
+ * @protected
76
+ * @param {T} value
77
+ */
78
+ setValue(value) {
79
+ this.wvalue = value;
80
+ }
81
+
82
+ /**
83
+ * Overrides `Object.prototype.valueOf()` to return the value stored in the Cell.
84
+ * @returns {T} The value of the Cell.
85
+ */
86
+ valueOf() {
87
+ return this.wvalue;
88
+ }
89
+
90
+ /**
91
+ * The value stored in the Cell.
92
+ * @protected @type {T}
93
+ */
94
+ get revalued() {
95
+ const currentlyComputedValue = activeComputedValues.at(-1);
96
+
97
+ if (currentlyComputedValue !== undefined) {
98
+ const isAlreadySubscribed = this.derivedCells.some(
99
+ (ref) => ref.deref() === currentlyComputedValue
100
+ );
101
+ if (isAlreadySubscribed) return this.wvalue;
102
+
103
+ this.derivedCells.push(new WeakRef(currentlyComputedValue));
104
+ }
105
+
106
+ return this.wvalue;
107
+ }
108
+
109
+ /**
110
+ * Sets a callback function that will be called whenever the value of the Cell changes.
111
+ * @param {(newValue: T) => void} callback - The function to be called when the value changes.
112
+ */
113
+ set onchange(callback) {
114
+ this.listen(callback);
115
+ }
116
+
117
+ /**
118
+ * Adds the provided effect callback to the list of effects for this cell, and returns a function that can be called to remove the effect.
119
+ * @param {(newValue: T) => void} callback - The effect callback to add.
120
+ * @param {EffectOptions} [options] - The options for the effect.
121
+ * @returns {() => void} A function that can be called to remove the effect.
122
+ */
123
+ listen(callback, options) {
124
+ let effect = callback;
125
+
126
+ if (options?.signal?.aborted) {
127
+ return () => {};
128
+ }
129
+
130
+ options?.signal?.addEventListener('abort', () => {
131
+ this.ignore(effect);
132
+ });
133
+
134
+ if (options?.once) {
135
+ effect = () => {
136
+ callback(this.wvalue);
137
+ this.ignore(effect);
138
+ };
139
+ }
140
+
141
+ if (options?.name) {
142
+ if (this.isListeningTo(options.name)) {
143
+ throw new Error(
144
+ `An effect with the name "${options.name}" is already listening to this cell.`
145
+ );
146
+ }
147
+ }
148
+
149
+ if (this.effects.some(({ effect }) => effect === callback)) {
150
+ throw new Error('This effect is already listening to this cell.');
151
+ }
152
+
153
+ this.effects.push({ effect, options });
154
+
155
+ this.effects.sort((a, b) => {
156
+ const aPriority = a.options?.priority ?? 0;
157
+ const bPriority = b.options?.priority ?? 0;
158
+ if (aPriority === bPriority) {
159
+ return 0;
160
+ }
161
+ return aPriority < bPriority ? 1 : -1;
162
+ });
163
+
164
+ return () => this.ignore(effect);
165
+ }
166
+
167
+ /**
168
+ * Creates an effect that is immediately executed with the current value of the cell, and then added to the list of effects for the cell.
169
+ * @param {(newValue: T) => void} effect - The effect callback to add.
170
+ * @returns {() => void} A function that can be called to remove the effect.
171
+ */
172
+ runAndListen(effect) {
173
+ effect(this.wvalue);
174
+ return this.listen(effect);
175
+ }
176
+
177
+ /**
178
+ * Removes the specified effect callback from the list of effects for this cell.
179
+ * @param {(newValue: T) => void} callback - The effect callback to remove.
180
+ */
181
+ ignore(callback) {
182
+ const index = this.effects.findIndex(({ effect }) => effect === callback);
183
+ if (index === -1) return;
184
+
185
+ this.effects.splice(index, 1);
186
+ }
187
+
188
+ /**
189
+ * Checks if the cell is listening to a watcher with the specified name.
190
+ * @param {string} name - The name of the watcher to check for.
191
+ * @returns {boolean} `true` if the cell is listening to a watcher with the specified name, `false` otherwise.
192
+ */
193
+ isListeningTo(name) {
194
+ return this.effects.some(({ options }) => options?.name === name);
195
+ }
196
+
197
+ /**
198
+ * Removes the watcher with the specified name from the list of effects for this cell.
199
+ * @param {string} name - The name of the watcher to stop listening to.
200
+ */
201
+ stopListeningTo(name) {
202
+ const effectIndex = this.effects.findIndex(
203
+ ({ options }) => options?.name === name
204
+ );
205
+ if (effectIndex === -1) return;
206
+
207
+ this.effects.splice(effectIndex, 1);
208
+ }
209
+
210
+ /**
211
+ * Updates the root object and notifies any registered watchers and computed dependents.
212
+ * This method is called whenever the root object's value changes.
213
+ */
214
+ update() {
215
+ // Run watchers.
216
+ for (const { effect: watcher } of this.effects) {
217
+ if (root.batchNestingLevel > 0) {
218
+ root.batchedEffects.set(watcher, [this.wvalue]);
219
+ continue;
220
+ }
221
+
222
+ watcher(this.wvalue);
223
+ }
224
+
225
+ // Run computed dependents.
226
+ const computedDependents = this.derivedCells;
227
+ if (computedDependents !== undefined) {
228
+ for (const dependent of computedDependents) {
229
+ dependent.deref()?.update();
230
+ }
231
+ }
232
+ // Periodically remove dead references.
233
+ this.derivedCells = this.derivedCells.filter(
234
+ (ref) => ref.deref() !== undefined
235
+ );
236
+
237
+ // global effects
238
+ for (const [options, effect] of root.globalPostEffects) {
239
+ if (options.ignoreDerivedCells && this instanceof DerivedCell) {
240
+ continue;
241
+ }
242
+
243
+ effect(this.wvalue);
244
+
245
+ if (options.runOnce) {
246
+ root.globalPostEffects = root.globalPostEffects.filter(
247
+ ([_, e]) => e !== effect
248
+ );
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Returns the current value of the cell without registering a watcher.
255
+ * @returns {T} - The current value of the cell.
256
+ */
257
+ peek() {
258
+ return this.wvalue;
259
+ }
260
+
261
+ /**
262
+ * Adds a global effect that runs before any Cell is updated.
263
+ * @param {(value: unknown) => void} effect - The effect function.
264
+ * @param {Partial<import('./root.js').GlobalEffectOptions>} [options] - The options for the effect.
265
+ * @example
266
+ * ```
267
+ * import { Cell } from '@adbl/cells';
268
+ *
269
+ * const cell = Cell.source(0);
270
+ * Cell.beforeUpdate((value) => console.log(value));
271
+ *
272
+ * cell.value = 1; // prints 1
273
+ * cell.value = 2; // prints 2
274
+ * ```
275
+ */
276
+ static beforeUpdate = (effect, options) => {
277
+ root.globalPreEffects.push([options ?? {}, effect]);
278
+ };
279
+
280
+ /**
281
+ * Adds a global post-update effect to the Cell system.
282
+ * @param {(value: unknown) => void} effect - The effect function to add.
283
+ * @param {Partial<import('./root.js').GlobalEffectOptions>} [options] - Options for the effect.
284
+ * @example
285
+ * ```
286
+ * import { Cell } from '@adbl/cells';
287
+ *
288
+ * const effect = (value) => console.log(value);
289
+ * Cell.afterUpdate(effect);
290
+ *
291
+ * const cell = Cell.source(0);
292
+ * cell.value = 1; // prints 1
293
+ * ```
294
+ */
295
+ static afterUpdate = (effect, options) => {
296
+ root.globalPostEffects.push([options ?? {}, effect]);
297
+ };
298
+
299
+ static removeGlobalEffects = () => {
300
+ root.globalPreEffects = [];
301
+ root.globalPostEffects = [];
302
+ };
303
+
304
+ /**
305
+ * Removes a global effect.
306
+ * @param {(value: unknown) => void} effect - The effect function added previously.
307
+ * @example
308
+ * ```
309
+ * import { Cell } from '@adbl/cells';
310
+ *
311
+ * const effect = (value) => console.log(value);
312
+ * Cell.beforeUpdate(effect);
313
+ *
314
+ * const cell = Cell.source(0);
315
+ * cell.value = 1; // prints 1
316
+ *
317
+ * Cell.removeGlobalEffect(effect);
318
+ *
319
+ * cell.value = 2; // prints nothing
320
+ * ```
321
+ */
322
+ static removeGlobalEffect = (effect) => {
323
+ root.globalPreEffects = root.globalPreEffects.filter(
324
+ ([_, e]) => e !== effect
325
+ );
326
+ };
327
+
328
+ /**
329
+ * @template T
330
+ * Creates a new Cell instance with the provided value.
331
+ * @param {T} value - The value to be stored in the Cell.
332
+ * @param {Partial<CellOptions<T>>} [options] - The options for the cell.
333
+ * @returns {SourceCell<T>} A new Cell instance.
334
+ * ```
335
+ * import { Cell } from '@adbl/cells';
336
+ *
337
+ * const cell = Cell.source('Hello world');
338
+ * console.log(cell.value); // Hello world.
339
+ *
340
+ * cell.value = 'Greetings!';
341
+ * console.log(cell.value) // Greetings!
342
+ * ```
343
+ */
344
+ static source = (value, options) => new SourceCell(value, options);
345
+
346
+ /**
347
+ * @template T
348
+ * Creates a new Derived instance with the provided callback function.
349
+ * @param {() => T} callback - The callback function to be used by the Derived instance.
350
+ * @returns {DerivedCell<T>} A new Derived instance.
351
+ * ```
352
+ * import { Cell } from '@adbl/cells';
353
+ *
354
+ * const cell = Cell.source(2);
355
+ * const derived = Cell.derived(() => cell.value * 2);
356
+ *
357
+ * console.log(derived.value); // 4
358
+ *
359
+ * cell.value = 3;
360
+ * console.log(derived.value); // 6
361
+ * ```
362
+ */
363
+ static derived = (callback) => new DerivedCell(callback);
364
+
365
+ /**
366
+ * Batches all the effects created to run only once.
367
+ * @param {() => void} callback - The function to be executed in a batched manner.
368
+ */
369
+ static batch = (callback) => {
370
+ root.batchNestingLevel++;
371
+ callback();
372
+ root.batchNestingLevel--;
373
+ if (root.batchNestingLevel === 0) {
374
+ for (const [effect, args] of root.batchedEffects) {
375
+ effect(...args);
376
+ }
377
+ root.batchedEffects = new Map();
378
+ }
379
+ };
380
+
381
+ /**
382
+ * Checks if the provided value is an instance of the Cell class.
383
+ * @param {any} value - The value to check.
384
+ * @returns {value is Cell<any>} True if the value is an instance of Cell, false otherwise.
385
+ */
386
+ static isCell = (value) => value instanceof Cell;
387
+
388
+ /**
389
+ * @template T
390
+ * Flattens the provided value by returning the value if it is not a Cell instance, or the value of the Cell instance if it is.
391
+ * @param {T | Cell<T>} value - The value to be flattened.
392
+ * @returns {T} The flattened value.
393
+ */
394
+ static flatten = (value) => {
395
+ // @ts-ignore:
396
+ return value instanceof Cell
397
+ ? Cell.flatten(value.wvalue)
398
+ : Array.isArray(value)
399
+ ? Cell.flattenArray(value)
400
+ : value instanceof Object
401
+ ? Cell.flattenObject(value)
402
+ : value;
403
+ };
404
+
405
+ /**
406
+ * Flattens an array by applying the `flatten` function to each element.
407
+ * @template T
408
+ * @param {Array<T | Cell<T>>} array - The array to be flattened.
409
+ * @returns {Array<T>} A new array with the flattened elements.
410
+ */
411
+ static flattenArray = (array) => array.map(Cell.flatten);
412
+
413
+ /**
414
+ * Flattens an object by applying the `flatten` function to each value.
415
+ * @template {object} T
416
+ * @param {T} object - The object to be flattened.
417
+ * @returns {{ [K in keyof T]: T[K] extends Cell<infer U> ? U : T[K] }} A new object with the flattened values.
418
+ */
419
+ static flattenObject = (object) => {
420
+ const result =
421
+ /** @type {{ [K in keyof T]: T[K] extends Cell<infer U> ? U : T[K] }} */ ({});
422
+ for (const [key, value] of Object.entries(object)) {
423
+ Reflect.set(result, key, Cell.flatten(value));
424
+ }
425
+ return result;
426
+ };
427
+
428
+ /**
429
+ * Wraps an asynchronous function with managed state.
430
+ *
431
+ * @template X - The type of the input parameter for the getter function.
432
+ * @template Y - The type of the output returned by the getter function.
433
+ * @param {(input: X) => Promise<Y>} getter - A function that performs the asynchronous operation.
434
+ * @returns {AsyncRequestAtoms<X, Y>} An object containing cells for pending, data, and error states,
435
+ * as well as functions to run and reload the operation.
436
+ *
437
+ * @example
438
+ * const { pending, data, error, run, reload } = Cell.async(async (input) => {
439
+ * const response = await fetch(`https://example.com/api/data?input=${input}`);
440
+ * return response.json();
441
+ * });
442
+ *
443
+ * run('input');
444
+ */
445
+ static async(getter) {
446
+ const pending = Cell.source(false);
447
+ const data = Cell.source(/** @type {Y | null} */ (null));
448
+ const error = Cell.source(/** @type {Error | null} */ (null));
449
+
450
+ /** @type {X | undefined} */
451
+ let initialInput = undefined;
452
+
453
+ async function run(input = initialInput) {
454
+ pending.value = true;
455
+ try {
456
+ initialInput = input;
457
+ const result = await getter(/** @type {X} */ (input));
458
+ data.value = result;
459
+ } catch (e) {
460
+ if (e instanceof Error) {
461
+ error.value = e;
462
+ } else {
463
+ throw e;
464
+ }
465
+ } finally {
466
+ pending.value = false;
467
+ }
468
+ }
469
+
470
+ /**
471
+ * @param {X} [newInput]
472
+ * @param {boolean} [changeLoadingState]
473
+ */
474
+ async function reload(newInput, changeLoadingState = true) {
475
+ if (changeLoadingState) {
476
+ pending.value = true;
477
+ }
478
+ try {
479
+ const result = await getter(
480
+ /** @type {X} */ (newInput ?? initialInput)
481
+ );
482
+ data.value = result;
483
+ } catch (e) {
484
+ if (e instanceof Error) {
485
+ error.value = e;
486
+ } else {
487
+ throw e;
488
+ }
489
+ } finally {
490
+ if (changeLoadingState) {
491
+ pending.value = false;
492
+ }
493
+ }
494
+ }
495
+
496
+ return {
497
+ pending,
498
+ data,
499
+ error,
500
+ run,
501
+ reload,
502
+ };
503
+ }
504
+ }
505
+
506
+ /**
507
+ * A class that represents a computed value that depends on other reactive values.
508
+ * The computed value is automatically updated when any of its dependencies change.
509
+ * @template T
510
+ * @extends {Cell<T>}
511
+ */
512
+ export class DerivedCell extends Cell {
513
+ /**
514
+ * @type {() => T}
515
+ * @protected
516
+ */
517
+ computedFn;
518
+
519
+ /**
520
+ * @param {() => T} computedFn - A function that generates the value of the computed.
521
+ */
522
+ constructor(computedFn) {
523
+ super();
524
+ this.computedFn = computedFn;
525
+ activeComputedValues.push(this);
526
+ this.setValue(computedFn());
527
+ activeComputedValues.pop();
528
+ }
529
+
530
+ /**
531
+ * @readonly
532
+ */
533
+ get value() {
534
+ return this.revalued;
535
+ }
536
+
537
+ /**
538
+ * @readonly
539
+ */
540
+ set value(_) {
541
+ throw new Error('Cannot set a derived Cell value.');
542
+ }
543
+
544
+ /**
545
+ * Updates the current value with the result of the computed function.
546
+ */
547
+ update() {
548
+ // global effects
549
+ for (const [options, effect] of root.globalPreEffects) {
550
+ if (options.ignoreDerivedCells) continue;
551
+
552
+ effect(this.wvalue);
553
+ }
554
+
555
+ if (root.batchNestingLevel > 0) {
556
+ root.batchedEffects.set(() => this.setValue(this.computedFn()), []);
557
+ } else {
558
+ this.setValue(this.computedFn());
559
+ }
560
+
561
+ super.update();
562
+ }
563
+ }
564
+
565
+ /**
566
+ * @template T
567
+ * @extends {Cell<T>}
568
+ */
569
+ export class SourceCell extends Cell {
570
+ /** @type {Partial<CellOptions<T>>} */
571
+ options;
572
+
573
+ /**
574
+ * Creates a new Cell with the provided value.
575
+ * @param {T} value
576
+ * @param {Partial<CellOptions<T>>} [options]
577
+ */
578
+ constructor(value, options) {
579
+ super();
580
+
581
+ this.setValue(options?.shallowProxied ? value : this.proxy(value));
582
+ this.options = options ?? {};
583
+ }
584
+
585
+ get value() {
586
+ return this.revalued;
587
+ }
588
+
589
+ /**
590
+ * Sets the value stored in the Cell and triggers an update.
591
+ * @param {T} value
592
+ */
593
+ set value(value) {
594
+ if (this.options.immutable) {
595
+ throw new Error('Cannot set the value of an immutable cell.');
596
+ }
597
+
598
+ const oldValue = this.wvalue;
599
+
600
+ const isEqual = this.options.equals
601
+ ? this.options.equals(oldValue, value)
602
+ : oldValue === value;
603
+
604
+ if (isEqual) return;
605
+
606
+ // global effects
607
+ for (const [options, effect] of root.globalPreEffects) {
608
+ effect(this.wvalue);
609
+
610
+ if (options.runOnce) {
611
+ root.globalPreEffects = root.globalPreEffects.filter(
612
+ ([_, e]) => e !== effect
613
+ );
614
+ }
615
+ }
616
+
617
+ this.setValue(value);
618
+ this.update();
619
+ }
620
+
621
+ /**
622
+ * Proxies the provided value deeply, allowing it to be observed and updated.
623
+ * @template T
624
+ * @param {T} value - The value to be proxied.
625
+ * @returns {T} - The proxied value.
626
+ * @private
627
+ */
628
+ proxy(value) {
629
+ if (typeof value !== 'object' || value === null) {
630
+ return value;
631
+ }
632
+
633
+ return new Proxy(value, {
634
+ get: (target, prop) => {
635
+ this.revalued;
636
+ return this.proxy(Reflect.get(target, prop));
637
+ },
638
+ set: (target, prop, value) => {
639
+ Reflect.set(target, prop, value);
640
+ this.update();
641
+ return true;
642
+ },
643
+ });
644
+ }
645
+ }
@@ -0,0 +1,18 @@
1
+ import { DerivedCell, SourceCell, Cell } from './classes.js';
2
+
3
+ /**
4
+ * Represents a partial map of cells, where each key in the object type `T` is mapped to either a `Cell<T[key]>` or the raw type `T[key]`.
5
+ * This type can be used to represent a partial set of cells for an object, where some properties are represented as cells and others are the raw values.
6
+ * @template {object} T The object type whose properties are mapped to cells or raw values.
7
+ * @typedef {{ [key in keyof T]: Cell<T[key]> | T[key] }} PartialCellMap
8
+ */
9
+
10
+ /**
11
+ * Represents a full set of cells for an object,
12
+ * where all properties are represented as cells.
13
+ * @template {object} T The object type whose properties are mapped to cells.
14
+ * @typedef {{ [key in keyof T]: Cell<T[key]> }} CellMap
15
+ */
16
+
17
+ export { SourceCell, DerivedCell, Cell };
18
+ export default Cell;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @typedef {import('./classes.js').Cell<any>} Watchable
3
+ * @typedef {import('./classes.js').DerivedCell<any>} DerivedCell
4
+ *
5
+ * @typedef GlobalEffectOptions
6
+ * @property {boolean} runOnce - Whether the effect should be removed after the first run.
7
+ * @property {boolean} ignoreDerivedCells - Whether the effect should be run even if the cell is a derived cell.
8
+ */
9
+
10
+ export const root = {
11
+ /**
12
+ * An array of global effects that run before a source Cell is updated.
13
+ * @type {[Partial<GlobalEffectOptions>, ((value: unknown) => void)][]}
14
+ */
15
+ globalPreEffects: [],
16
+
17
+ /**
18
+ * An array of global effects that run after a source Cell is updated.
19
+ * @type {[Partial<GlobalEffectOptions>, ((value: unknown) => void)][]}
20
+ */
21
+ globalPostEffects: [],
22
+
23
+ /**
24
+ * The nesting level of batch operations.
25
+ * This will prevent nested batch operations from triggering effects when they finish.
26
+ * @type {number}
27
+ */
28
+ batchNestingLevel: 0,
29
+
30
+ /**
31
+ * A map of effect tuples to be executed in a batch.
32
+ * The key in each entry is the effect, and the value is the list of arguments call it with.
33
+ * All callbacks in this map will be executed only once in a batch.
34
+ * @type {Map<Function, any[]>}
35
+ */
36
+ batchedEffects: new Map(),
37
+ };
38
+
39
+ /**
40
+ * A value representing the computed values that are currently being calculated.
41
+ * It is an array so it can keep track of nested computed values.
42
+ * @type {DerivedCell[]}
43
+ */
44
+ export const activeComputedValues = [];
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@adbl/cells",
3
+ "version": "0.0.0",
4
+ "description": "A simple implementation of reactive updates for JavaScript",
5
+ "main": "index.js",
6
+ "private": false,
7
+ "type": "module",
8
+ "module": "./index.js",
9
+ "typings": "./types/index.d.ts",
10
+ "scripts": {
11
+ "test": "vitest",
12
+ "types": "tsc --p jsconfig.json"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/adebola-io/signals.git"
17
+ },
18
+ "keywords": [
19
+ "reactive",
20
+ "signals",
21
+ "events"
22
+ ],
23
+ "author": "Sefunmi Adebola Akomolafe",
24
+ "license": "MIT",
25
+ "bugs": {
26
+ "url": "https://github.com/adebola-io/signals/issues"
27
+ },
28
+ "homepage": "https://github.com/adebola-io/signals#readme",
29
+ "devDependencies": {
30
+ "typescript": "^5.4.5",
31
+ "vitest": "^1.6.0"
32
+ }
33
+ }
@@ -0,0 +1 @@
1
+ export * from "./library/index.js";
@@ -0,0 +1,324 @@
1
+ /**
2
+ * @template T
3
+ */
4
+ export class Cell<T> {
5
+ /**
6
+ * Adds a global effect that runs before any Cell is updated.
7
+ * @param {(value: unknown) => void} effect - The effect function.
8
+ * @param {Partial<import('./root.js').GlobalEffectOptions>} [options] - The options for the effect.
9
+ * @example
10
+ * ```
11
+ * import { Cell } from '@adbl/cells';
12
+ *
13
+ * const cell = Cell.source(0);
14
+ * Cell.beforeUpdate((value) => console.log(value));
15
+ *
16
+ * cell.value = 1; // prints 1
17
+ * cell.value = 2; // prints 2
18
+ * ```
19
+ */
20
+ static beforeUpdate: (effect: (value: unknown) => void, options?: Partial<import("./root.js").GlobalEffectOptions> | undefined) => void;
21
+ /**
22
+ * Adds a global post-update effect to the Cell system.
23
+ * @param {(value: unknown) => void} effect - The effect function to add.
24
+ * @param {Partial<import('./root.js').GlobalEffectOptions>} [options] - Options for the effect.
25
+ * @example
26
+ * ```
27
+ * import { Cell } from '@adbl/cells';
28
+ *
29
+ * const effect = (value) => console.log(value);
30
+ * Cell.afterUpdate(effect);
31
+ *
32
+ * const cell = Cell.source(0);
33
+ * cell.value = 1; // prints 1
34
+ * ```
35
+ */
36
+ static afterUpdate: (effect: (value: unknown) => void, options?: Partial<import("./root.js").GlobalEffectOptions> | undefined) => void;
37
+ static removeGlobalEffects: () => void;
38
+ /**
39
+ * Removes a global effect.
40
+ * @param {(value: unknown) => void} effect - The effect function added previously.
41
+ * @example
42
+ * ```
43
+ * import { Cell } from '@adbl/cells';
44
+ *
45
+ * const effect = (value) => console.log(value);
46
+ * Cell.beforeUpdate(effect);
47
+ *
48
+ * const cell = Cell.source(0);
49
+ * cell.value = 1; // prints 1
50
+ *
51
+ * Cell.removeGlobalEffect(effect);
52
+ *
53
+ * cell.value = 2; // prints nothing
54
+ * ```
55
+ */
56
+ static removeGlobalEffect: (effect: (value: unknown) => void) => void;
57
+ /**
58
+ * @template T
59
+ * Creates a new Cell instance with the provided value.
60
+ * @param {T} value - The value to be stored in the Cell.
61
+ * @param {Partial<CellOptions<T>>} [options] - The options for the cell.
62
+ * @returns {SourceCell<T>} A new Cell instance.
63
+ * ```
64
+ * import { Cell } from '@adbl/cells';
65
+ *
66
+ * const cell = Cell.source('Hello world');
67
+ * console.log(cell.value); // Hello world.
68
+ *
69
+ * cell.value = 'Greetings!';
70
+ * console.log(cell.value) // Greetings!
71
+ * ```
72
+ */
73
+ static source: <T_1>(value: T_1, options?: Partial<CellOptions<T_1>> | undefined) => SourceCell<T_1>;
74
+ /**
75
+ * @template T
76
+ * Creates a new Derived instance with the provided callback function.
77
+ * @param {() => T} callback - The callback function to be used by the Derived instance.
78
+ * @returns {DerivedCell<T>} A new Derived instance.
79
+ * ```
80
+ * import { Cell } from '@adbl/cells';
81
+ *
82
+ * const cell = Cell.source(2);
83
+ * const derived = Cell.derived(() => cell.value * 2);
84
+ *
85
+ * console.log(derived.value); // 4
86
+ *
87
+ * cell.value = 3;
88
+ * console.log(derived.value); // 6
89
+ * ```
90
+ */
91
+ static derived: <T_2>(callback: () => T_2) => DerivedCell<T_2>;
92
+ /**
93
+ * Batches all the effects created to run only once.
94
+ * @param {() => void} callback - The function to be executed in a batched manner.
95
+ */
96
+ static batch: (callback: () => void) => void;
97
+ /**
98
+ * Checks if the provided value is an instance of the Cell class.
99
+ * @param {any} value - The value to check.
100
+ * @returns {value is Cell<any>} True if the value is an instance of Cell, false otherwise.
101
+ */
102
+ static isCell: (value: any) => value is Cell<any>;
103
+ /**
104
+ * @template T
105
+ * Flattens the provided value by returning the value if it is not a Cell instance, or the value of the Cell instance if it is.
106
+ * @param {T | Cell<T>} value - The value to be flattened.
107
+ * @returns {T} The flattened value.
108
+ */
109
+ static flatten: <T_3>(value: T_3 | Cell<T_3>) => T_3;
110
+ /**
111
+ * Flattens an array by applying the `flatten` function to each element.
112
+ * @template T
113
+ * @param {Array<T | Cell<T>>} array - The array to be flattened.
114
+ * @returns {Array<T>} A new array with the flattened elements.
115
+ */
116
+ static flattenArray: <T_4>(array: (T_4 | Cell<T_4>)[]) => T_4[];
117
+ /**
118
+ * Flattens an object by applying the `flatten` function to each value.
119
+ * @template {object} T
120
+ * @param {T} object - The object to be flattened.
121
+ * @returns {{ [K in keyof T]: T[K] extends Cell<infer U> ? U : T[K] }} A new object with the flattened values.
122
+ */
123
+ static flattenObject: <T_5 extends object>(object: T_5) => { [K in keyof T_5]: T_5[K] extends Cell<infer U> ? U : T_5[K]; };
124
+ /**
125
+ * Wraps an asynchronous function with managed state.
126
+ *
127
+ * @template X - The type of the input parameter for the getter function.
128
+ * @template Y - The type of the output returned by the getter function.
129
+ * @param {(input: X) => Promise<Y>} getter - A function that performs the asynchronous operation.
130
+ * @returns {AsyncRequestAtoms<X, Y>} An object containing cells for pending, data, and error states,
131
+ * as well as functions to run and reload the operation.
132
+ *
133
+ * @example
134
+ * const { pending, data, error, run, reload } = Cell.async(async (input) => {
135
+ * const response = await fetch(`https://example.com/api/data?input=${input}`);
136
+ * return response.json();
137
+ * });
138
+ *
139
+ * run('input');
140
+ */
141
+ static async<X, Y>(getter: (input: X) => Promise<Y>): AsyncRequestAtoms<X, Y>;
142
+ /**
143
+ * @type {Array<({
144
+ * effect: (newValue: T) => void,
145
+ * options?: EffectOptions,
146
+ * })>}
147
+ * @protected
148
+ */
149
+ protected effects: Array<({
150
+ effect: (newValue: T) => void;
151
+ options?: EffectOptions;
152
+ })>;
153
+ /**
154
+ * @type {Array<WeakRef<DerivedCell<any>>>}
155
+ * @protected
156
+ */
157
+ protected derivedCells: Array<WeakRef<DerivedCell<any>>>;
158
+ /**
159
+ * @protected @type T
160
+ */
161
+ protected wvalue: T;
162
+ /**
163
+ * @protected
164
+ * @param {T} value
165
+ */
166
+ protected setValue(value: T): void;
167
+ /**
168
+ * Overrides `Object.prototype.valueOf()` to return the value stored in the Cell.
169
+ * @returns {T} The value of the Cell.
170
+ */
171
+ valueOf(): T;
172
+ /**
173
+ * The value stored in the Cell.
174
+ * @protected @type {T}
175
+ */
176
+ protected get revalued(): T;
177
+ /**
178
+ * Sets a callback function that will be called whenever the value of the Cell changes.
179
+ * @param {(newValue: T) => void} callback - The function to be called when the value changes.
180
+ */
181
+ set onchange(callback: (newValue: T) => void);
182
+ /**
183
+ * Adds the provided effect callback to the list of effects for this cell, and returns a function that can be called to remove the effect.
184
+ * @param {(newValue: T) => void} callback - The effect callback to add.
185
+ * @param {EffectOptions} [options] - The options for the effect.
186
+ * @returns {() => void} A function that can be called to remove the effect.
187
+ */
188
+ listen(callback: (newValue: T) => void, options?: EffectOptions | undefined): () => void;
189
+ /**
190
+ * Creates an effect that is immediately executed with the current value of the cell, and then added to the list of effects for the cell.
191
+ * @param {(newValue: T) => void} effect - The effect callback to add.
192
+ * @returns {() => void} A function that can be called to remove the effect.
193
+ */
194
+ runAndListen(effect: (newValue: T) => void): () => void;
195
+ /**
196
+ * Removes the specified effect callback from the list of effects for this cell.
197
+ * @param {(newValue: T) => void} callback - The effect callback to remove.
198
+ */
199
+ ignore(callback: (newValue: T) => void): void;
200
+ /**
201
+ * Checks if the cell is listening to a watcher with the specified name.
202
+ * @param {string} name - The name of the watcher to check for.
203
+ * @returns {boolean} `true` if the cell is listening to a watcher with the specified name, `false` otherwise.
204
+ */
205
+ isListeningTo(name: string): boolean;
206
+ /**
207
+ * Updates the root object and notifies any registered watchers and computed dependents.
208
+ * This method is called whenever the root object's value changes.
209
+ */
210
+ update(): void;
211
+ /**
212
+ * Returns the current value of the cell without registering a watcher.
213
+ * @returns {T} - The current value of the cell.
214
+ */
215
+ peek(): T;
216
+ }
217
+ /**
218
+ * A class that represents a computed value that depends on other reactive values.
219
+ * The computed value is automatically updated when any of its dependencies change.
220
+ * @template T
221
+ * @extends {Cell<T>}
222
+ */
223
+ export class DerivedCell<T> extends Cell<T> {
224
+ /**
225
+ * @param {() => T} computedFn - A function that generates the value of the computed.
226
+ */
227
+ constructor(computedFn: () => T);
228
+ /**
229
+ * @type {() => T}
230
+ * @protected
231
+ */
232
+ protected computedFn: () => T;
233
+ /**
234
+ * @readonly
235
+ */
236
+ readonly set value(_: T);
237
+ /**
238
+ * @readonly
239
+ */
240
+ readonly get value(): T;
241
+ }
242
+ /**
243
+ * @template T
244
+ * @extends {Cell<T>}
245
+ */
246
+ export class SourceCell<T> extends Cell<T> {
247
+ /**
248
+ * Creates a new Cell with the provided value.
249
+ * @param {T} value
250
+ * @param {Partial<CellOptions<T>>} [options]
251
+ */
252
+ constructor(value: T, options?: Partial<CellOptions<T>> | undefined);
253
+ /** @type {Partial<CellOptions<T>>} */
254
+ options: Partial<CellOptions<T>>;
255
+ /**
256
+ * Sets the value stored in the Cell and triggers an update.
257
+ * @param {T} value
258
+ */
259
+ set value(value: T);
260
+ get value(): T;
261
+ /**
262
+ * Proxies the provided value deeply, allowing it to be observed and updated.
263
+ * @template T
264
+ * @param {T} value - The value to be proxied.
265
+ * @returns {T} - The proxied value.
266
+ * @private
267
+ */
268
+ private proxy;
269
+ }
270
+ export type AsyncRequestAtoms<Input, Output> = {
271
+ /**
272
+ * Represents the loading state of an asynchronous request.
273
+ */
274
+ pending: SourceCell<boolean>;
275
+ /**
276
+ * Represents the data returned by the asynchronous request.
277
+ */
278
+ data: SourceCell<Output | null>;
279
+ /**
280
+ * Represents the errors returned by the asynchronous request, if any.
281
+ */
282
+ error: SourceCell<Error | null>;
283
+ /**
284
+ * Triggers the asynchronous request.
285
+ */
286
+ run: NeverIfAny<Input> extends never ? (input?: Input) => Promise<void> : (input: Input) => Promise<void>;
287
+ /**
288
+ * Triggers the asynchronous request again with an optional new input and optionally changes the loading state.
289
+ */
290
+ reload: (newInput?: Input, changeLoadingState?: boolean) => Promise<void>;
291
+ };
292
+ export type EffectOptions = {
293
+ /**
294
+ * Whether the effect should be removed after the first run.
295
+ */
296
+ once?: boolean | undefined;
297
+ /**
298
+ * An AbortSignal to be used to ignore the effect if it is aborted.
299
+ */
300
+ signal?: AbortSignal | undefined;
301
+ /**
302
+ * The name of the effect for debugging purposes.
303
+ */
304
+ name?: string | undefined;
305
+ /**
306
+ * The priority of the effect. Higher priority effects are executed first. The default priority is 0.
307
+ */
308
+ priority?: number | undefined;
309
+ };
310
+ export type CellOptions<T> = {
311
+ /**
312
+ * Whether the cell should be immutable. If set to true, the cell will not allow updates and will throw an error if the value is changed.
313
+ */
314
+ immutable?: boolean | undefined;
315
+ /**
316
+ * Whether the cell's value should be shallowly proxied. If set to true, the cell will only proxy the top-level properties of the value, preventing any changes to nested properties. This can be useful for performance optimizations.
317
+ */
318
+ shallowProxied?: boolean | undefined;
319
+ /**
320
+ * A function that determines whether two values are equal. If not provided, the default equality function will be used.
321
+ */
322
+ equals?: ((oldValue: T, newValue: T) => boolean) | undefined;
323
+ };
324
+ export type NeverIfAny<T> = 0 extends (1 & T) ? never : T;
@@ -0,0 +1,15 @@
1
+ export default Cell;
2
+ /**
3
+ * Represents a partial map of cells, where each key in the object type `T` is mapped to either a `Cell<T[key]>` or the raw type `T[key]`.
4
+ * This type can be used to represent a partial set of cells for an object, where some properties are represented as cells and others are the raw values.
5
+ */
6
+ export type PartialCellMap<T extends object> = { [key in keyof T]: T[key] | Cell<T[key]>; };
7
+ /**
8
+ * Represents a full set of cells for an object,
9
+ * where all properties are represented as cells.
10
+ */
11
+ export type CellMap<T extends object> = { [key in keyof T]: Cell<T[key]>; };
12
+ import { SourceCell } from './classes.js';
13
+ import { DerivedCell } from './classes.js';
14
+ import { Cell } from './classes.js';
15
+ export { SourceCell, DerivedCell, Cell };
@@ -0,0 +1,24 @@
1
+ export namespace root {
2
+ let globalPreEffects: [Partial<GlobalEffectOptions>, ((value: unknown) => void)][];
3
+ let globalPostEffects: [Partial<GlobalEffectOptions>, ((value: unknown) => void)][];
4
+ let batchNestingLevel: number;
5
+ let batchedEffects: Map<Function, any[]>;
6
+ }
7
+ /**
8
+ * A value representing the computed values that are currently being calculated.
9
+ * It is an array so it can keep track of nested computed values.
10
+ * @type {DerivedCell[]}
11
+ */
12
+ export const activeComputedValues: import("./classes.js").DerivedCell<any>[];
13
+ export type Watchable = import('./classes.js').Cell<any>;
14
+ export type DerivedCell = import('./classes.js').DerivedCell<any>;
15
+ export type GlobalEffectOptions = {
16
+ /**
17
+ * - Whether the effect should be removed after the first run.
18
+ */
19
+ runOnce: boolean;
20
+ /**
21
+ * - Whether the effect should be run even if the cell is a derived cell.
22
+ */
23
+ ignoreDerivedCells: boolean;
24
+ };