├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── tests.yml
├── .gitignore
├── .npmignore
├── .npmrc
├── .prettierignore
├── .prettierrc.cjs
├── LICENSE
├── README.md
├── dist
├── index.d.ts
├── index.d.ts.map
├── index.js
├── index.js.map
├── index.test.d.ts
├── index.test.d.ts.map
├── index.test.js
└── index.test.js.map
├── lume.config.cjs
├── package.json
├── src
├── index.test.ts
└── index.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | indent_style = space
9 | indent_size = 4
10 | end_of_line = lf
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.operating-system }}
8 |
9 | strategy:
10 | matrix:
11 | # TODO get tests working in Windows
12 | # windows-latest
13 | operating-system: [ubuntu-latest, macos-latest]
14 |
15 | steps:
16 | - uses: actions/checkout@v1
17 | - name: Use Node.js latest
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: latest
21 | - name: install
22 | run: |
23 | npm i
24 | - name: check formatting
25 | run: |
26 | npm run prettier:check
27 | - name: build
28 | run: |
29 | npm run clean
30 | npm run build
31 | - name: test
32 | run: |
33 | npm test
34 | - name: check repo is clean
35 | # skip this check in windows for now, as the build outputs may get slightly modified in Windows, which we want to fix.
36 | if: runner.os != 'Windows'
37 | run: |
38 | git add . && git diff --quiet && git diff --cached --quiet
39 | env:
40 | CI: true
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | package-lock.json
3 | .vscode/
4 | *.log
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Specificty of the following rules matters.
2 |
3 | # Ignore everything,
4 | /**/*
5 |
6 | # but include these folders
7 | !/dist/**/*
8 | !/src/**/*
9 |
10 | # except for these files in the above folders.
11 | /dist/**/*.test.*
12 | /dist/tests/**/*
13 | /src/**/*.test.*
14 | /src/tests/**/*
15 |
16 | # The following won't work as you think it would.
17 | # /**/*
18 | # !/dist/**/*
19 | # !/src/**/*
20 | # /**/*.test.*
21 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
--------------------------------------------------------------------------------
/.prettierrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = require('@lume/cli/.prettierrc.js')
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Joseph Orbegoso Pea (joe@trusktr.io)
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATED, use [solid-js](https://solidjs.com/) and [classy-solid](https://github.com/lume/classy-solid) directly instead
2 |
3 | # @lume/variable
4 |
5 | Make reactive variables and react to their changes.
6 |
7 | #### `npm install @lume/variable --save`
8 |
9 | ## React to changes in reactive variables
10 |
11 | In the following we make a reactive variable `count`, increment its value every
12 | second, and re-run a piece of code that logs the value to the console
13 | on each change:
14 |
15 | ```js
16 | import {variable, autorun} from '@lume/variable'
17 |
18 | const count = variable(0)
19 |
20 | setInterval(() => count(count() + 1), 1000)
21 |
22 | autorun(() => {
23 | // Log the count variable any time it changes.
24 | console.log(count())
25 | })
26 | ```
27 |
28 | The function passed into `autorun` (sometimes referred to as "an autorun" or "a
29 | computation") automatically re-runs every second due to `count` being
30 | incremented every second.
31 |
32 | Calling `count()` gets the current value, while calling `count(123)` with an
33 | arg sets the value. Thus `count(count() + 1)` increments the value.
34 |
35 | Any reactive variables used inside an autorun function are registered (or
36 | "tracked") as "dependencies" by `autorun`, then any time those dependencies
37 | change, the autorun re-runs.
38 |
39 | We call this "dependency-tracking reactivity".
40 |
41 | An autorun with multiple variables accessed inside of it will re-run any time
42 | any of the accessed variables change:
43 |
44 | ```js
45 | autorun(() => {
46 | // Log these variables every time any of them change.
47 | console.log(firstName(), lastName(), age(), hairColor())
48 | })
49 | ```
50 |
51 | `autorun`s can be grouped in any way we like:
52 |
53 | ```js
54 | autorun(() => {
55 | // This re-runs only when firstName or lastName have changed.
56 | console.log(firstName(), lastName())
57 | })
58 |
59 | autorun(() => {
60 | // This re-runs only when age or hairColor have changed.
61 | console.log(age(), hairColor())
62 | })
63 | ```
64 |
65 | If we wish to stop an `autorun` from re-running, we can call its returned stop
66 | function (note, this is not necessary if we or the JS engine no longer have
67 | references to any of the reactive variables that are dependencies of the
68 | `autorun`, and in that case everything will be garbage collected and will no
69 | longer re-run):
70 |
71 | ```js
72 | import {variable, autorun} from '@lume/variable'
73 |
74 | const count = variable(0)
75 |
76 | setInterval(() => count(count() + 1), 1000)
77 |
78 | const stop = autorun(() => {
79 | // Log the count variable any time it changes.
80 | console.log(count())
81 | })
82 |
83 | // Stop the autorun (and therefore no more logging will happen) after 5 seconds:
84 | setTimeout(stop, 5000)
85 | ```
86 |
87 | ## Power and Simplicity
88 |
89 | Learn how dependency-tracking reactivity makes your code cleaner and more
90 | concise compared to another more common pattern.
91 |
92 | Click to expand.
93 |
94 |
95 |
96 | Reactive computations (autoruns) are nice because it doesn't matter how we
97 | group our variables (dependencies) within computations. What matters is we
98 | write what we care about (expressions using our variables) without having
99 | to think about how to wire reactivity up.
100 |
101 | With an event-based pattern, in contrast, our code would be more verbose and less
102 | convenient.
103 |
104 | Looking back at our simple autorun for logging several variables,
105 |
106 | ```js
107 | autorun(() => {
108 | // Log these variables every time any of them change.
109 | console.log(firstName(), lastName(), age(), hairColor())
110 | })
111 | ```
112 |
113 | we will see that writing the same thing with some sort of event pattern is more verbose:
114 |
115 | ```js
116 | function log() {
117 | // Log these variables every time any of them change.
118 | console.log(firstName.value, lastName.value, age.value, hairColor.value)
119 | }
120 |
121 | // We need to also register an event handler for each value we care to react to:
122 | firstName.on('change', log)
123 | lastName.on('change', log)
124 | age.on('change', log)
125 | hairColor.on('change', log)
126 | ```
127 |
128 | With this hypothetical event pattern, we had to share our logging function with
129 | each event emitter in order to wire up the reactivity, having us write more
130 | code. Using `autorun` was simpler and less verbose.
131 |
132 | Now let's say we want to add one more item to the `console.log` statement.
133 |
134 | Here is what that looks like with an autorun:
135 |
136 | ```js
137 | autorun(() => {
138 | // Log these variables every time any of them change.
139 | console.log(firstName(), lastName(), age(), hairColor(), favoriteFood())
140 | })
141 | ```
142 |
143 | With an event emitter pattern, there is more to do:
144 |
145 | ```js
146 | function log() {
147 | // Log these variables every time any of them change.
148 | console.log(firstName.value, lastName.value, age.value, hairColor.value, favoriteFood.value)
149 | }
150 |
151 | firstName.on('change', log)
152 | lastName.on('change', log)
153 | age.on('change', log)
154 | hairColor.on('change', log)
155 | favoriteFood.on('change', log) // <-------- Don't forget to add this line too!
156 | ```
157 |
158 | Not only is the event pattern more verbose, but it is more error prone because
159 | we can forget to register the event handler: we had to modify the code in two
160 | places in order to add logging of the `favoriteFood` value.
161 |
162 | Here's where it gets interesting!
163 |
164 | Reactive computations allow us to decouple the reactivity implementation from
165 | places where we need reactivity, and to focus on the code we want to write.
166 |
167 | Let's say we want to make a class with properties, and abserve any of them when
168 | they change.
169 |
170 | First, let's use a familiar event pattern to show the less-than-ideal scenario first:
171 |
172 | ```js
173 | // Let's say this is in a lib called 'events'.
174 | class EventEmitter {
175 | addEventHandler(eventName, fn) {
176 | /*...use imagination here...*/
177 | }
178 | removeEventHandler(eventName, fn) {
179 | /*...use imagination here...*/
180 | }
181 | emit(eventName, data) {
182 | /*...use imagination here...*/
183 | }
184 | }
185 | ```
186 |
187 | Now let's use `EventEmitter` to make a class whose poperties we can observe the
188 | changes of. In the following class, we'll make getter/setter pairs so that any
189 | time a setter is used to set a value, it will emit a "change" event.
190 |
191 | ```js
192 | import {EventEmitter} from 'events'
193 |
194 | // We need to extend from EventEmitter (or compose it inside the class, but the amount
195 | // of code would be similar).
196 | class Martian extends EventEmitter {
197 | _firstName = ''
198 | get firstName() {
199 | return this._firstName
200 | }
201 | set firstName(v) {
202 | this._firstName = v
203 | this.emit('change', 'firstName') // Emit any time the property is set.
204 | }
205 |
206 | _lastName = ''
207 | get lastName() {
208 | return this._lastName
209 | }
210 | set lastName(v) {
211 | this._lastName = v
212 | this.emit('change', 'lastName')
213 | }
214 |
215 | _age = 0
216 | get age() {
217 | return this._age
218 | }
219 | set age(v) {
220 | this._age = v
221 | this.emit('change', 'age')
222 | }
223 |
224 | _hairColor = ''
225 | get hairColor() {
226 | return this._hairColor
227 | }
228 | set hairColor(v) {
229 | this._hairColor = v
230 | this.emit('change', 'hairColor')
231 | }
232 |
233 | _favoriteFood = ''
234 | get favoriteFood() {
235 | return this._favoriteFood
236 | }
237 | set favoriteFood(v) {
238 | this._favoriteFood = v
239 | this.emit('change', 'favoriteFood')
240 | }
241 | }
242 |
243 | const martian = new Martian()
244 | ```
245 |
246 | The following shows how we would react to changes in three of the five properties of a `Martian`:
247 |
248 | ```js
249 | martian.addEventHandler('change', property => {
250 | if (['firstName', 'hairColor', 'favoriteFood'].includes(property)) {
251 | // Log these three variables every time any of the three change.
252 | console.log(martian.firstName, martian.hairColor, martian.favoriteFood)
253 | }
254 | })
255 | ```
256 |
257 | It works, but we can still make this better while still using the same event
258 | pattern.
259 |
260 | Let's say we want to make it more efficient: instead of all event handlers
261 | being subscribed to a single `change` event (because Martians probably have
262 | lots and lots of properties) and filtering for the properties we care to
263 | observe, we can choose specific event names for each property and subscribe
264 | handlers to specific property events:
265 |
266 | ```js
267 | import {EventEmitter} from 'events'
268 |
269 | class Martian extends EventEmitter {
270 | _firstName = ''
271 | get firstName() {
272 | return this._firstName
273 | }
274 | set firstName(v) {
275 | this._firstName = v
276 | this.emit('change:firstName') // Emit a specific event for the firstName property.
277 | }
278 |
279 | _lastName = ''
280 | get lastName() {
281 | return this._lastName
282 | }
283 | set lastName(v) {
284 | this._lastName = v
285 | this.emit('change:lastName') // Similar for the lastName property.
286 | }
287 |
288 | _age = 0
289 | get age() {
290 | return this._age
291 | }
292 | set age(v) {
293 | this._age = v
294 | this.emit('change:age') // And so on.
295 | }
296 |
297 | _hairColor = ''
298 | get hairColor() {
299 | return this._hairColor
300 | }
301 | set hairColor(v) {
302 | this._hairColor = v
303 | this.emit('change:hairColor')
304 | }
305 |
306 | _favoriteFood = ''
307 | get favoriteFood() {
308 | return this._favoriteFood
309 | }
310 | set favoriteFood(v) {
311 | this._favoriteFood = v
312 | this.emit('change:favoriteFood')
313 | }
314 | }
315 | ```
316 |
317 | We can now avoid the overhead of the array filtering we previously had with the `.includes` check:
318 |
319 | ```js
320 | const martian = new Martian()
321 |
322 | const onChange = () => {
323 | // Log these three variables every time any of the three change.
324 | console.log(martian.firstName, martian.hairColor, martian.favoriteFood)
325 | }
326 |
327 | martian.addEventHandler('change:firstName', onChange)
328 | martian.addEventHandler('change:hairColor', onChange)
329 | martian.addEventHandler('change:favoriteFood', onChange)
330 | ```
331 |
332 | This is better than before because now if other properties besides the ones
333 | we've subscribed to change, the event pattern won't be calling our function
334 | needlessly and we won't be doing property name checks every time.
335 |
336 | We can still do better with the event pattern! (Spoiler: it won't get as clean
337 | as with `autorun` below, which we'll get to next.)
338 |
339 | We can come up with an automatic event-wiring mechanism. It could look
340 | something like the following:
341 |
342 | ```js
343 | import {EventEmitter, WithEventProps} from 'events'
344 |
345 | // Imagine `WithEventProps` wires up events for any properties specified in a
346 | // static `eventProps` field:
347 | const Martian = WithEventProps(
348 | class Martian extends EventEmitter {
349 | static eventProps = ['firstName', 'lastName', 'age', 'hairColor', 'favoriteFood']
350 |
351 | firstName = ''
352 | lastName = ''
353 | age = 0
354 | hairColor = ''
355 | favoriteFood = ''
356 | },
357 | )
358 |
359 | // Listen to events as before:
360 |
361 | const martian = new Martian()
362 |
363 | const onChange = () => {
364 | // Log these three variables every time any of the three change.
365 | console.log(martian.firstName, martian.hairColor, martian.favoriteFood)
366 | }
367 |
368 | martian.addEventHandler('change:firstName', onChange)
369 | martian.addEventHandler('change:hairColor', onChange)
370 | martian.addEventHandler('change:favoriteFood', onChange)
371 | ```
372 |
373 | That is a lot shorter already, but we can still do better! (It still won't be
374 | as simple as with dependency-tracking reactivity, which is coming up.)
375 |
376 | We can make the event pattern more
377 | [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) ("Don't Repeat
378 | Yourself") using decorators to allow us to be less repetitive:
379 |
380 | ```js
381 | import {EventEmitter, emits} from 'events'
382 |
383 | // Imagine this `@emits` decorator wires up an event for each decorated property.
384 | @emits
385 | class Martian extends EventEmitter {
386 | @emits firstName = ''
387 | @emits lastName = ''
388 | @emits age = 0
389 | @emits hairColor = ''
390 | @emits favoriteFood = ''
391 | }
392 |
393 | // Listen to events as before:
394 |
395 | const martian = new Martian()
396 |
397 | const onChange = () => {
398 | // Log these three variables every time any of the three change.
399 | console.log(martian.firstName, martian.hairColor, martian.favoriteFood)
400 | }
401 |
402 | martian.addEventHandler('change:firstName', onChange)
403 | martian.addEventHandler('change:hairColor', onChange)
404 | martian.addEventHandler('change:favoriteFood', onChange)
405 | ```
406 |
407 | This is better than before because now we didn't have to repeat the property
408 | names twice, reducing the chance of errors from mismatched names. Instead we
409 | labeled them all with a decorator.
410 |
411 | ---
412 |
413 | We can still do better! 🤯
414 |
415 | With LUME's reactive variables we can further decouple a class's implementation from
416 | the reactivity mechanism and make things cleaner.
417 |
418 | We can re-write the previous non-decorator example (and still not using
419 | decorators) so that our class does not need to extend from a particular base
420 | class to inherit a reactivity implementation:
421 |
422 | ```js
423 | import {variable, autorun} from '@lume/variable'
424 |
425 | // This class does not extend from any base class. Instead, reactive variables
426 | // are defined inside the class.
427 | class Martian {
428 | firstName = variable('')
429 | lastName = variable('')
430 | age = variable(0)
431 | hairColor = variable('')
432 | favoriteFood = variable('')
433 | }
434 |
435 | const martian = new Martian()
436 |
437 | autorun(() => {
438 | // Log these three variables every time any of the three change.
439 | console.log(martian.firstName(), martian.hairColor(), martian.favoriteFood())
440 | })
441 | ```
442 |
443 | This is better than before because the reactivity is not an inherent part of
444 | our class hierarchy, instead being a feature of the reactive variables. We can
445 | use this form of reactivity in our `Matrian` class or in any other class
446 | without having class inheritance requirements, and other developers do not have
447 | to make subclasses of our classes just to have reactivity.
448 |
449 | Plus, we did not need to subscribe an event listener to specific
450 | events like we did earlier with the `addEventHandler` calls. Instead, we
451 | wrapped our function with `autorun` and it became a "reactive computation" with
452 | the ability to re-run when its dependencies (the reactive variables used within
453 | it) change.
454 |
455 | ...We can still do better! 🤯...
456 |
457 | Using LUME's decorators, the experience is as good as it gets:
458 |
459 | ```js
460 | import {variable, autorun, reactive} from '@lume/variable'
461 |
462 | // Now we mark the class and properties as reactive with the `@reactive` decorator.
463 | @reactive
464 | class Martian {
465 | @reactive firstName = ''
466 | @reactive lastName = ''
467 | @reactive age = 0
468 | @reactive hairColor = ''
469 | @reactive favoriteFood = ''
470 |
471 | // This property is not reactive, as it is not marked with `@reactive`.
472 | cryogenesis = false
473 | }
474 |
475 | const martian = new Martian()
476 |
477 | autorun(() => {
478 | // Log these four variables every time any of the first three change. Note
479 | // that this will not automatically rerun when cryogenesis changes because cryogenesis
480 | // is not reactive.
481 | console.log(martian.firstName, martian.hairColor, martian.favoriteFood, martian.cryogenesis)
482 | })
483 | ```
484 |
485 | This is better than before because now we can use the properties like regular
486 | properties instead of having to call them as functions to read their values
487 | like we had to in the prior example. We can write `this.age` instead of
488 | `this.age()` for reading a value, and `this.age = 10` instead of `this.age(10)`
489 | for writing a value.
490 |
491 | Dependency-tracking reactivity makes things nice and concise.
492 |
493 |
494 |
495 | ## API
496 |
497 | ### `const myVar = variable(value)`
498 |
499 | Creates a reactive variable with an initial `value`. The return value is a function that
500 |
501 | - when called with no argument, returns the reactive variable's value, f.e. `myVar()`.
502 | - when called with an argument, sets the reactive variable's value, f.e. `myVar(newValue)`.
503 |
504 | ```js
505 | const foo = variable(false)
506 | const bar = variable(123)
507 | ```
508 |
509 | ### `const stop = autorun(fn)`
510 |
511 | Takes a function `fn` and immediately runs it, while tracking any reactive
512 | variables that were used inside of it as dependencies. Any time those variables
513 | change, `fn` is executed again. Each time `fn` re-runs, dependencies are
514 | re-tracked, which means that conditional branching within `fn` can change which
515 | dependencies will re-run `fn` next time.
516 |
517 | `autorun` returns a `stop` function that when called causes `fn` never to be
518 | automatically executed again. This is useful when you no longer care about some
519 | variables.
520 |
521 | ```js
522 | autorun(() => {
523 | if (foo()) doSomethingWith(bar())
524 | })
525 | ```
526 |
527 | ### `@reactive`
528 |
529 | A decorator that makes properties in a class reactive. Besides decorating
530 | properties with the decorator, also be sure to decorate the class that shall
531 | have reactive variable with the same decorator as well.
532 |
533 | ```js
534 | @reactive
535 | class Car {
536 | @reactive engineOn = false
537 | @reactive sound = 'vroom'
538 | }
539 |
540 | const car = new Car()
541 |
542 | autorun(() => {
543 | // Any time the car turns on, it makes a sound.
544 | if (car.engineOn) console.log(car.sound)
545 | })
546 | ```
547 |
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | export interface VariableGetter {
2 | (): T;
3 | }
4 | export interface VariableSetter {
5 | (value: T): T;
6 | }
7 | export interface Variable extends Iterable | VariableSetter> {
8 | (value?: undefined): T;
9 | (value: T): T;
10 | (value?: T): void | T;
11 | get: VariableGetter;
12 | set: VariableSetter;
13 | [0]: VariableGetter;
14 | [1]: VariableSetter;
15 | [Symbol.iterator](): IterableIterator | VariableSetter>;
16 | }
17 | export declare function variable(value: T): [VariableGetter, VariableSetter] & Variable;
18 | export type Computation = (previousValue?: unknown) => unknown;
19 | export type StopFunction = () => void;
20 | export declare function autorun(f: Computation): StopFunction;
21 | export declare function reactive(protoOrClassElement: any, propName?: string, _descriptor?: PropertyDescriptor): any;
22 | export declare function _trackReactiveProperty(Class: AnyClassWithReactiveProps, propName: string): void;
23 | type AnyClass = new (...args: any[]) => object;
24 | type AnyClassWithReactiveProps = (new (...args: any[]) => object) & {
25 | reactiveProperties?: string[];
26 | __isReactive__?: true;
27 | };
28 | export declare function reactify(obj: T, props: (keyof T)[]): typeof obj;
29 | export declare function reactify(obj: InstanceType, ctor: C): typeof obj;
30 | export declare function circular(first: VariableGetter, setFirst: (v: Type) => void, second: VariableGetter, setSecond: (v: Type) => void): StopFunction;
31 | export declare const version = "0.10.1";
32 | export {};
33 | //# sourceMappingURL=index.d.ts.map
--------------------------------------------------------------------------------
/dist/index.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc,CAAC,CAAC;IAChC,IAAI,CAAC,CAAA;CACL;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAChC,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAA;CACb;AAGD,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAEzF,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,CAAC,CAAA;IAEtB,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAA;IACb,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAA;IAErB,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,CAAA;IACtB,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,CAAA;IAGtB,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAA;IACtB,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAA;IACtB,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;CAC5E;AAoBD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,wDA2BnC;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,aAAa,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;AAC9D,MAAM,MAAM,YAAY,GAAG,MAAM,IAAI,CAAA;AAerC,wBAAgB,OAAO,CAAC,CAAC,EAAE,WAAW,GAAG,YAAY,CASpD;AAED,wBAAgB,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,kBAAkB,GAAG,GAAG,CAiC3G;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,yBAAyB,EAAE,QAAQ,EAAE,MAAM,QAGxF;AAqJD,KAAK,QAAQ,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAA;AAC9C,KAAK,yBAAyB,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,GAAG;IACnE,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B,cAAc,CAAC,EAAE,IAAI,CAAA;CACrB,CAAA;AAID,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,OAAO,GAAG,CAAA;AACnE,wBAAgB,QAAQ,CAAC,CAAC,SAAS,QAAQ,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,GAAG,CAAA;AAyCvF,wBAAgB,QAAQ,CAAC,IAAI,EAC5B,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,EAC3B,QAAQ,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,IAAI,EAC3B,MAAM,EAAE,cAAc,CAAC,IAAI,CAAC,EAC5B,SAAS,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,IAAI,GAC1B,YAAY,CAmBd;AAED,eAAO,MAAM,OAAO,WAAW,CAAA"}
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | import { getInheritedDescriptor } from 'lowclass';
2 | import { createSignal, createEffect, createRoot, untrack, getListener } from 'solid-js';
3 | function readVariable() {
4 | return this();
5 | }
6 | function writeVariable(value) {
7 | return this(value);
8 | }
9 | export function variable(value) {
10 | const [get, set] = createSignal(value, { equals: false });
11 | const variable = ((value) => {
12 | if (typeof value === 'undefined')
13 | return get();
14 | set(() => value);
15 | return value;
16 | });
17 | const getter = readVariable.bind(variable);
18 | const setter = writeVariable.bind(variable);
19 | variable.get = getter;
20 | variable.set = setter;
21 | variable[0] = getter;
22 | variable[1] = setter;
23 | variable[Symbol.iterator] = function* () {
24 | yield variable[0];
25 | yield variable[1];
26 | };
27 | return variable;
28 | }
29 | export function autorun(f) {
30 | let stop;
31 | createRoot(dispose => {
32 | stop = dispose;
33 | createEffect(f);
34 | });
35 | return stop;
36 | }
37 | export function reactive(protoOrClassElement, propName, _descriptor) {
38 | const isDecoratorV2 = arguments.length === 1 && 'kind' in protoOrClassElement;
39 | if (isDecoratorV2) {
40 | const classElement = protoOrClassElement;
41 | if (classElement.kind === 'class')
42 | return { ...classElement, finisher: reactiveClassFinisher };
43 | return {
44 | ...classElement,
45 | finisher(Class) {
46 | _trackReactiveProperty(Class, classElement.key);
47 | return classElement.finisher?.(Class) ?? Class;
48 | },
49 | };
50 | }
51 | if (arguments.length === 1 && typeof protoOrClassElement === 'function') {
52 | const Class = protoOrClassElement;
53 | return reactiveClassFinisher(Class);
54 | }
55 | const Class = protoOrClassElement.constructor;
56 | _trackReactiveProperty(Class, propName);
57 | }
58 | export function _trackReactiveProperty(Class, propName) {
59 | if (!Class.reactiveProperties || !Class.hasOwnProperty('reactiveProperties'))
60 | Class.reactiveProperties = [];
61 | if (!Class.reactiveProperties.includes(propName))
62 | Class.reactiveProperties.push(propName);
63 | }
64 | function reactiveClassFinisher(Class) {
65 | if (Class.hasOwnProperty('__isReactive__'))
66 | return Class;
67 | return class ReactiveDecoratorFinisher extends Class {
68 | static __isReactive__ = true;
69 | constructor(...args) {
70 | if (getListener()) {
71 | return untrack(() => {
72 | const self = Reflect.construct(Class, args, new.target);
73 | reactify(self, Class);
74 | return self;
75 | });
76 | }
77 | super(...args);
78 | reactify(this, Class);
79 | }
80 | };
81 | }
82 | function _reactive(obj, propName) {
83 | if (typeof propName !== 'string')
84 | throw new Error('TODO: support for non-string fields with @reactive decorator');
85 | const vName = 'v_' + propName;
86 | let descriptor = getInheritedDescriptor(obj, propName);
87 | let originalGet;
88 | let originalSet;
89 | let initialValue;
90 | if (descriptor) {
91 | originalGet = descriptor.get;
92 | originalSet = descriptor.set;
93 | if (originalGet || originalSet) {
94 | if (!originalGet || !originalSet) {
95 | console.warn('The `@reactive` decorator was used on an accessor named "' +
96 | propName +
97 | '" which had a getter or a setter, but not both. Reactivity on accessors works only when accessors have both get and set. In this case the decorator does not do anything.');
98 | return;
99 | }
100 | delete descriptor.get;
101 | delete descriptor.set;
102 | }
103 | else {
104 | initialValue = descriptor.value;
105 | if (!descriptor.writable) {
106 | console.warn('The `@reactive` decorator was used on a property named ' +
107 | propName +
108 | ' that is not writable. Reactivity is not enabled for non-writable properties.');
109 | return;
110 | }
111 | delete descriptor.value;
112 | delete descriptor.writable;
113 | }
114 | }
115 | descriptor = {
116 | configurable: true,
117 | enumerable: true,
118 | ...descriptor,
119 | get: originalGet
120 | ? function () {
121 | const v = __getReactiveVar(this, vName, initialValue);
122 | v();
123 | return originalGet.call(this);
124 | }
125 | : function () {
126 | const v = __getReactiveVar(this, vName, initialValue);
127 | return v();
128 | },
129 | set: originalSet
130 | ? function (newValue) {
131 | originalSet.call(this, newValue);
132 | const v = __getReactiveVar(this, vName);
133 | v(newValue);
134 | if (!this.__propsSetAtLeastOnce__)
135 | this.__propsSetAtLeastOnce__ = new Set();
136 | this.__propsSetAtLeastOnce__.add(propName);
137 | }
138 | : function (newValue) {
139 | const v = __getReactiveVar(this, vName);
140 | v(newValue);
141 | if (!this.__propsSetAtLeastOnce__)
142 | this.__propsSetAtLeastOnce__ = new Set();
143 | this.__propsSetAtLeastOnce__.add(propName);
144 | },
145 | };
146 | if (!obj.__reactifiedProps__)
147 | obj.__reactifiedProps__ = new Set();
148 | obj.__reactifiedProps__.add(propName);
149 | Object.defineProperty(obj, propName, descriptor);
150 | }
151 | function __getReactiveVar(instance, vName, initialValue = undefined) {
152 | let v = instance[vName];
153 | if (v)
154 | return v;
155 | instance[vName] = v = variable(initialValue);
156 | return v;
157 | }
158 | export function reactify(obj, propsOrClass) {
159 | if (isClass(propsOrClass)) {
160 | const Class = propsOrClass;
161 | const props = Class.reactiveProperties;
162 | if (Array.isArray(props))
163 | createReactiveAccessors(obj, props);
164 | }
165 | else {
166 | const props = propsOrClass;
167 | createReactiveAccessors(obj, props);
168 | }
169 | return obj;
170 | }
171 | function isClass(obj) {
172 | return typeof obj == 'function';
173 | }
174 | function createReactiveAccessors(obj, props) {
175 | for (const prop of props) {
176 | if (obj.__reactifiedProps__?.has(prop))
177 | continue;
178 | const initialValue = obj[prop];
179 | _reactive(obj, prop);
180 | obj[prop] = initialValue;
181 | }
182 | }
183 | export function circular(first, setFirst, second, setSecond) {
184 | let initial = true;
185 | const stop1 = autorun(() => {
186 | const v = first();
187 | if (initial && !(initial = false))
188 | setSecond(v);
189 | else
190 | initial = true;
191 | });
192 | const stop2 = autorun(() => {
193 | const v = second();
194 | if (initial && !(initial = false))
195 | setFirst(v);
196 | else
197 | initial = true;
198 | });
199 | return function stop() {
200 | stop1();
201 | stop2();
202 | };
203 | }
204 | export const version = '0.10.1';
205 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/dist/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/index.test.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | function expect(...args: any[]): any;
3 | }
4 | export {};
5 | //# sourceMappingURL=index.test.d.ts.map
--------------------------------------------------------------------------------
/dist/index.test.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAKA,OAAO,CAAC,MAAM,CAAC;IACd,SAAS,MAAM,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAAA;CACpC"}
--------------------------------------------------------------------------------
/dist/index.test.js:
--------------------------------------------------------------------------------
1 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5 | return c > 3 && r && Object.defineProperty(target, key, r), r;
6 | };
7 | import { untrack } from 'solid-js';
8 | import { variable, autorun, reactive, reactify, circular } from './index.js';
9 | describe('@lume/variable', () => {
10 | describe('variable()', () => {
11 | it('has gettable and settable values', async () => {
12 | expect(variable).toBeInstanceOf(Function);
13 | const num = variable(0);
14 | num(1);
15 | expect(num()).toBe(1);
16 | num(num() + 1);
17 | expect(num()).toBe(2);
18 | num.set(3);
19 | expect(num.get()).toBe(3);
20 | });
21 | it('object destructuring convenience', async () => {
22 | let count;
23 | {
24 | const { get, set } = variable(0);
25 | count = get;
26 | Promise.resolve().then(() => {
27 | set(1);
28 | set(count() + 1);
29 | set(3);
30 | });
31 | }
32 | let expectedCount = -1;
33 | autorun(() => {
34 | expectedCount++;
35 | expect(count()).toBe(expectedCount);
36 | });
37 | await Promise.resolve();
38 | expect(count()).toBe(3);
39 | expect(expectedCount).toBe(3);
40 | });
41 | it('array destructuring convenience', async () => {
42 | let count;
43 | {
44 | const [get, set] = variable(0);
45 | count = get;
46 | Promise.resolve().then(() => {
47 | set(1);
48 | set(count() + 1);
49 | set(3);
50 | });
51 | }
52 | let expectedCount = -1;
53 | autorun(() => {
54 | expectedCount++;
55 | expect(count()).toBe(expectedCount);
56 | });
57 | await Promise.resolve();
58 | expect(count()).toBe(3);
59 | expect(expectedCount).toBe(3);
60 | });
61 | });
62 | describe('circular()', () => {
63 | it('allows two variables to be synced to each other (two-way binding)', () => {
64 | const number = variable(0);
65 | const double = variable(0);
66 | let count = 0;
67 | autorun(() => {
68 | count++;
69 | number();
70 | double();
71 | });
72 | circular(number, () => number(double() / 2), double, () => double(number() * 2));
73 | expect(count).toBe(2);
74 | number(2);
75 | expect(count).toBe(4);
76 | expect(number()).toBe(2);
77 | expect(double()).toBe(4);
78 | double(2);
79 | expect(count).toBe(6);
80 | expect(number()).toBe(1);
81 | expect(double()).toBe(2);
82 | });
83 | });
84 | describe('autorun()', () => {
85 | it('re-runs on changes of variables used within, and can be stopped from re-running', () => {
86 | expect(autorun).toBeInstanceOf(Function);
87 | const count = variable(0);
88 | let runCount = 0;
89 | const stop = autorun(() => {
90 | count();
91 | runCount++;
92 | });
93 | count(1);
94 | count(2);
95 | expect(runCount).toBe(3);
96 | stop();
97 | count(3);
98 | count(4);
99 | expect(runCount).toBe(3);
100 | });
101 | });
102 | describe('@reactive and reactify', () => {
103 | it('is a function', () => {
104 | expect(reactive).toBeInstanceOf(Function);
105 | });
106 | it('does not prevent superclass constructor from receiving subclass constructor args', () => {
107 | let Insect = class Insect {
108 | result;
109 | constructor(result) {
110 | this.result = result;
111 | }
112 | };
113 | Insect = __decorate([
114 | reactive
115 | ], Insect);
116 | class Butterfly extends Insect {
117 | constructor(arg) {
118 | super(arg * 2);
119 | }
120 | }
121 | const b = new Butterfly(4);
122 | expect(b.result).toBe(8);
123 | });
124 | it('makes class properties reactive, using class and property/accessor decorators', () => {
125 | let Butterfly = class Butterfly {
126 | colors = 3;
127 | _wingSize = 2;
128 | get wingSize() {
129 | return this._wingSize;
130 | }
131 | set wingSize(s) {
132 | this._wingSize = s;
133 | }
134 | };
135 | __decorate([
136 | reactive
137 | ], Butterfly.prototype, "colors", void 0);
138 | __decorate([
139 | reactive
140 | ], Butterfly.prototype, "wingSize", null);
141 | Butterfly = __decorate([
142 | reactive
143 | ], Butterfly);
144 | const b = new Butterfly();
145 | testButterflyProps(b);
146 | });
147 | it('show that reactify makes an infinite reactivity loop when used manually', () => {
148 | class Foo {
149 | amount = 3;
150 | constructor() {
151 | reactify(this, ['amount']);
152 | }
153 | }
154 | class Bar extends Foo {
155 | double = 0;
156 | constructor() {
157 | super();
158 | reactify(this, ['double']);
159 | this.double = this.amount * 2;
160 | }
161 | }
162 | let count = 0;
163 | function loop() {
164 | autorun(() => {
165 | new Bar();
166 | count++;
167 | });
168 | }
169 | count;
170 | expect(loop).toThrowError(RangeError);
171 | expect(count).toBeGreaterThan(1);
172 | });
173 | it('show how to manually untrack constructors when not using decorators', () => {
174 | class Foo {
175 | amount = 3;
176 | constructor() {
177 | reactify(this, ['amount']);
178 | }
179 | }
180 | class Bar extends Foo {
181 | double = 0;
182 | constructor() {
183 | super();
184 | reactify(this, ['double']);
185 | untrack(() => {
186 | this.double = this.amount * 2;
187 | });
188 | }
189 | }
190 | let count = 0;
191 | function noLoop() {
192 | autorun(() => {
193 | new Bar();
194 | count++;
195 | });
196 | }
197 | expect(noLoop).not.toThrow();
198 | expect(count).toBe(1);
199 | });
200 | it('automatically does not track reactivity in constructors when using decorators', () => {
201 | let Foo = class Foo {
202 | amount = 3;
203 | };
204 | __decorate([
205 | reactive
206 | ], Foo.prototype, "amount", void 0);
207 | Foo = __decorate([
208 | reactive
209 | ], Foo);
210 | let Bar = class Bar extends Foo {
211 | double = 0;
212 | constructor() {
213 | super();
214 | this.double = this.amount * 2;
215 | }
216 | };
217 | __decorate([
218 | reactive
219 | ], Bar.prototype, "double", void 0);
220 | Bar = __decorate([
221 | reactive
222 | ], Bar);
223 | let b;
224 | let count = 0;
225 | function noLoop() {
226 | autorun(() => {
227 | b = new Bar();
228 | count++;
229 | });
230 | }
231 | expect(noLoop).not.toThrow();
232 | const b2 = b;
233 | b.amount = 4;
234 | expect(b).toBe(b2);
235 | expect(count).toBe(1);
236 | });
237 | it('automatically does not track reactivity in constructors when using decorators even when not the root most decorator', () => {
238 | let Foo = class Foo {
239 | amount = 3;
240 | };
241 | __decorate([
242 | reactive
243 | ], Foo.prototype, "amount", void 0);
244 | Foo = __decorate([
245 | reactive
246 | ], Foo);
247 | function someOtherDecorator(Class) {
248 | console.log(Class);
249 | if (arguments.length === 1 && 'kind' in Class && Class.kind === 'class')
250 | return { ...Class, finisher: (Klass) => class Foo extends Klass {
251 | } };
252 | return class Foo extends Class {
253 | };
254 | }
255 | let Bar = class Bar extends Foo {
256 | double = 0;
257 | constructor() {
258 | super();
259 | this.double = this.amount * 2;
260 | }
261 | };
262 | __decorate([
263 | reactive
264 | ], Bar.prototype, "double", void 0);
265 | Bar = __decorate([
266 | someOtherDecorator,
267 | reactive
268 | ], Bar);
269 | let b;
270 | let count = 0;
271 | function noLoop() {
272 | autorun(() => {
273 | b = new Bar();
274 | count++;
275 | });
276 | }
277 | expect(noLoop).not.toThrow();
278 | const b2 = b;
279 | b.amount = 4;
280 | expect(b).toBe(b2);
281 | expect(count).toBe(1);
282 | });
283 | it('makes class properties reactive, not using any decorators, specified in the constructor', () => {
284 | class Butterfly {
285 | colors = 3;
286 | _wingSize = 2;
287 | get wingSize() {
288 | return this._wingSize;
289 | }
290 | set wingSize(s) {
291 | this._wingSize = s;
292 | }
293 | constructor() {
294 | reactify(this, ['colors', 'wingSize']);
295 | }
296 | }
297 | const b = new Butterfly();
298 | testButterflyProps(b);
299 | });
300 | it('makes class properties reactive, with properties defined in the constructor', () => {
301 | class Butterfly {
302 | colors;
303 | _wingSize;
304 | get wingSize() {
305 | return this._wingSize;
306 | }
307 | set wingSize(s) {
308 | this._wingSize = s;
309 | }
310 | constructor() {
311 | this.colors = 3;
312 | this._wingSize = 2;
313 | reactify(this, ['colors', 'wingSize']);
314 | }
315 | }
316 | const b = new Butterfly();
317 | testButterflyProps(b);
318 | });
319 | it('makes class properties reactive, using only class decorator, specified via static prop', () => {
320 | let Butterfly = class Butterfly {
321 | static reactiveProperties = ['colors', 'wingSize'];
322 | colors = 3;
323 | _wingSize = 2;
324 | get wingSize() {
325 | return this._wingSize;
326 | }
327 | set wingSize(s) {
328 | this._wingSize = s;
329 | }
330 | };
331 | Butterfly = __decorate([
332 | reactive
333 | ], Butterfly);
334 | const b = new Butterfly();
335 | testButterflyProps(b);
336 | });
337 | it('makes class properties reactive, using only class decorator, specified via static prop, properties defined in the constructor', () => {
338 | let Butterfly = class Butterfly {
339 | static reactiveProperties = ['colors', 'wingSize'];
340 | colors;
341 | _wingSize;
342 | get wingSize() {
343 | return this._wingSize;
344 | }
345 | set wingSize(s) {
346 | this._wingSize = s;
347 | }
348 | constructor() {
349 | this.colors = 3;
350 | this._wingSize = 2;
351 | }
352 | };
353 | Butterfly = __decorate([
354 | reactive
355 | ], Butterfly);
356 | const b = new Butterfly();
357 | testButterflyProps(b);
358 | });
359 | it('makes class properties reactive, not using any decorators, specified via static prop', () => {
360 | class Butterfly {
361 | static reactiveProperties = ['colors', 'wingSize'];
362 | colors = 3;
363 | _wingSize = 2;
364 | get wingSize() {
365 | return this._wingSize;
366 | }
367 | set wingSize(s) {
368 | this._wingSize = s;
369 | }
370 | constructor() {
371 | reactify(this, Butterfly);
372 | }
373 | }
374 | const b = new Butterfly();
375 | testButterflyProps(b);
376 | });
377 | it('makes class properties reactive, not using any decorators, specified via static prop, properties defined in the constructor', () => {
378 | class Butterfly {
379 | static reactiveProperties = ['colors', 'wingSize'];
380 | colors;
381 | _wingSize;
382 | get wingSize() {
383 | return this._wingSize;
384 | }
385 | set wingSize(s) {
386 | this._wingSize = s;
387 | }
388 | constructor() {
389 | this.colors = 3;
390 | this._wingSize = 2;
391 | reactify(this, Butterfly);
392 | }
393 | }
394 | const b = new Butterfly();
395 | testButterflyProps(b);
396 | });
397 | it('can be used on a function-style class, with properties in the constructor', () => {
398 | function Butterfly() {
399 | this.colors = 3;
400 | this._wingSize = 2;
401 | reactify(this, Butterfly);
402 | }
403 | Butterfly.reactiveProperties = ['colors', 'wingSize'];
404 | Butterfly.prototype = {
405 | get wingSize() {
406 | return this._wingSize;
407 | },
408 | set wingSize(s) {
409 | this._wingSize = s;
410 | },
411 | };
412 | const b = new Butterfly();
413 | testButterflyProps(b);
414 | });
415 | it('can be used on a function-style class, with properties on the prototype, reactify with static reactiveProperties in constructor', () => {
416 | function Butterfly() {
417 | reactify(this, Butterfly);
418 | }
419 | Butterfly.reactiveProperties = ['colors', 'wingSize'];
420 | Butterfly.prototype = {
421 | colors: 3,
422 | _wingSize: 2,
423 | get wingSize() {
424 | return this._wingSize;
425 | },
426 | set wingSize(s) {
427 | this._wingSize = s;
428 | },
429 | };
430 | const b = new Butterfly();
431 | testButterflyProps(b);
432 | });
433 | it('can be used on a function-style class, with properties on the prototype, reactify with static reactiveProperties on the prototype', () => {
434 | function Butterfly() { }
435 | Butterfly.reactiveProperties = ['colors', 'wingSize'];
436 | Butterfly.prototype = {
437 | colors: 3,
438 | _wingSize: 2,
439 | get wingSize() {
440 | return this._wingSize;
441 | },
442 | set wingSize(s) {
443 | this._wingSize = s;
444 | },
445 | };
446 | reactify(Butterfly.prototype, Butterfly);
447 | const b = new Butterfly();
448 | testButterflyProps(b);
449 | });
450 | it('can be used on a function-style class, with properties on the prototype, reactify with specific props in constructor', () => {
451 | function Butterfly() {
452 | reactify(this, ['colors', 'wingSize']);
453 | }
454 | Butterfly.prototype = {
455 | colors: 3,
456 | _wingSize: 2,
457 | get wingSize() {
458 | return this._wingSize;
459 | },
460 | set wingSize(s) {
461 | this._wingSize = s;
462 | },
463 | };
464 | const b = new Butterfly();
465 | testButterflyProps(b);
466 | });
467 | it('can be used on a function-style class, with properties on the prototype, reactify with specific props on the prototype', () => {
468 | function Butterfly() { }
469 | Butterfly.prototype = {
470 | colors: 3,
471 | _wingSize: 2,
472 | get wingSize() {
473 | return this._wingSize;
474 | },
475 | set wingSize(s) {
476 | this._wingSize = s;
477 | },
478 | };
479 | reactify(Butterfly.prototype, ['colors', 'wingSize']);
480 | const b = new Butterfly();
481 | testButterflyProps(b);
482 | });
483 | it('can be used on a function-style class, with properties in the constructor, reactive applied to constructor', () => {
484 | let Butterfly = function Butterfly() {
485 | this.colors = 3;
486 | this._wingSize = 2;
487 | };
488 | Butterfly.reactiveProperties = ['colors', 'wingSize'];
489 | Butterfly.prototype = {
490 | get wingSize() {
491 | return this._wingSize;
492 | },
493 | set wingSize(s) {
494 | this._wingSize = s;
495 | },
496 | };
497 | Butterfly = reactive(Butterfly);
498 | const b = new Butterfly();
499 | testButterflyProps(b);
500 | });
501 | it('can be used on a function-style class, with properties on the prototype, reactive applied to constructor', () => {
502 | let Butterfly = function Butterfly() { };
503 | Butterfly.reactiveProperties = ['colors', 'wingSize'];
504 | Butterfly.prototype = {
505 | colors: 3,
506 | _wingSize: 2,
507 | get wingSize() {
508 | return this._wingSize;
509 | },
510 | set wingSize(s) {
511 | this._wingSize = s;
512 | },
513 | };
514 | Butterfly = reactive(Butterfly);
515 | const b = new Butterfly();
516 | testButterflyProps(b);
517 | });
518 | it('can be used on a function-style class, with properties in the constructor, reactive applied to specific prototype properties', () => {
519 | let Butterfly = function Butterfly() {
520 | this.colors = 3;
521 | this._wingSize = 2;
522 | };
523 | Butterfly.reactiveProperties = ['colors', 'wingSize'];
524 | Butterfly.prototype = {
525 | get wingSize() {
526 | return this._wingSize;
527 | },
528 | set wingSize(s) {
529 | this._wingSize = s;
530 | },
531 | };
532 | reactive(Butterfly.prototype, 'colors');
533 | reactive(Butterfly.prototype, 'wingSize');
534 | Butterfly = reactive(Butterfly);
535 | const b = new Butterfly();
536 | testButterflyProps(b);
537 | });
538 | it('can be used on a function-style class, with properties on the prototype, reactive applied to specific prototype properties', () => {
539 | let Butterfly = function Butterfly() { };
540 | Butterfly.reactiveProperties = ['colors', 'wingSize'];
541 | Butterfly.prototype = {
542 | colors: 3,
543 | _wingSize: 2,
544 | get wingSize() {
545 | return this._wingSize;
546 | },
547 | set wingSize(s) {
548 | this._wingSize = s;
549 | },
550 | };
551 | reactive(Butterfly.prototype, 'colors');
552 | reactive(Butterfly.prototype, 'wingSize');
553 | Butterfly = reactive(Butterfly);
554 | const b = new Butterfly();
555 | testButterflyProps(b);
556 | });
557 | });
558 | });
559 | function testButterflyProps(b) {
560 | let count = 0;
561 | autorun(() => {
562 | b.colors;
563 | b.wingSize;
564 | count++;
565 | });
566 | expect(b.colors).toBe(3);
567 | expect(b.wingSize).toBe(2);
568 | expect(b._wingSize).toBe(2);
569 | expect(count).toBe(1);
570 | b.colors++;
571 | expect(b.colors).toBe(4);
572 | expect(count).toBe(2);
573 | b.wingSize++;
574 | expect(b.wingSize).toBe(3);
575 | expect(b._wingSize).toBe(3);
576 | expect(count).toBe(3);
577 | }
578 | //# sourceMappingURL=index.test.js.map
--------------------------------------------------------------------------------
/dist/index.test.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/lume.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testWithAllTSAndBabelDecoratorBuildConfigurations: true,
3 |
4 | importMap: {
5 | imports: {
6 | lowclass: '/node_modules/lowclass/dist/index.js',
7 | 'solid-js': '/node_modules/solid-js/dist/solid.js',
8 | 'solid-js/web': '/node_modules/solid-js/web/dist/web.js',
9 | 'solid-js/html': '/node_modules/solid-js/html/dist/html.js',
10 | 'solid-js/store': '/node_modules/solid-js/store/dist/store.js',
11 | },
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@lume/variable",
3 | "version": "0.10.1",
4 | "description": "Create reactive variables and react their changes.",
5 | "author": "Joe Pea ",
6 | "license": "MIT",
7 | "homepage": "http://github.com/lume/variable#readme",
8 | "type": "module",
9 | "main": "dist/index.js",
10 | "types": "dist/index.d.ts",
11 | "scripts": {
12 | "LUME SCRIPTS XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX": "",
13 | "clean": "lume clean",
14 | "build": "lume build",
15 | "dev": "lume dev",
16 | "typecheck": "lume typecheck",
17 | "typecheck:watch": "lume typecheckWatch",
18 | "test": "lume test",
19 | "test:watch": "lume test --watch",
20 | "prettier": "lume prettier",
21 | "prettier:check": "lume prettierCheck",
22 | "release:patch": "lume releasePatch",
23 | "release:minor": "lume releaseMinor",
24 | "release:major": "lume releaseMajor",
25 | "version": "lume versionHook",
26 | "postversion": "lume postVersionHook"
27 | },
28 | "dependencies": {
29 | "lowclass": "^6.0.0",
30 | "solid-js": "<1.5.0"
31 | },
32 | "devDependencies": {
33 | "@lume/cli": "^0.10.0",
34 | "prettier": "3.0.3",
35 | "typescript": "^5.0.0"
36 | },
37 | "repository": {
38 | "type": "git",
39 | "url": "git+ssh://git@github.com/lume/variable.git"
40 | },
41 | "bugs": {
42 | "url": "https://github.com/lume/variable/issues"
43 | },
44 | "keywords": [
45 | "frp",
46 | "functional reactive programming",
47 | "functional-reactive-programming",
48 | "reactive programming",
49 | "reactive-programming",
50 | "reactive coding",
51 | "reactive-coding",
52 | "reactive variables",
53 | "reactive-variables",
54 | "reactivity",
55 | "reactive computation",
56 | "reactive-computation"
57 | ]
58 | }
59 |
--------------------------------------------------------------------------------
/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import {untrack} from 'solid-js'
2 | import {variable, autorun, reactive, reactify, circular} from './index.js'
3 |
4 | // TODO move type def to @lume/cli, map @types/jest's `expect` type into the
5 | // global env.
6 | declare global {
7 | function expect(...args: any[]): any
8 | }
9 |
10 | describe('@lume/variable', () => {
11 | describe('variable()', () => {
12 | it('has gettable and settable values', async () => {
13 | expect(variable).toBeInstanceOf(Function)
14 | const num = variable(0)
15 |
16 | // Set the variable's value by passing a value in.
17 | num(1)
18 |
19 | // Read the variable's value by calling it with no args.
20 | expect(num()).toBe(1)
21 |
22 | // increment example:
23 | num(num() + 1)
24 |
25 | expect(num()).toBe(2)
26 |
27 | // An alternative way to set and get the variable's values, if you
28 | // prefer.
29 | num.set(3)
30 | expect(num.get()).toBe(3)
31 | })
32 |
33 | it('object destructuring convenience', async () => {
34 | let count: () => number
35 |
36 | // Example: The following block scope exposes only the getter.
37 | {
38 | // The get and set functions are useful, for example, if you'd
39 | // like to expose only one or the other to external code, but
40 | // not both (making them write-only or read-only).
41 | const {get, set} = variable(0)
42 |
43 | // Expose the getter to the outside.
44 | count = get
45 |
46 | Promise.resolve().then(() => {
47 | set(1)
48 | set(count() + 1)
49 | set(3)
50 | })
51 | }
52 |
53 | // On the outside, we can only read the variable's value.
54 | let expectedCount = -1
55 | autorun(() => {
56 | expectedCount++
57 | expect(count()).toBe(expectedCount)
58 | })
59 |
60 | await Promise.resolve()
61 |
62 | expect(count()).toBe(3)
63 | expect(expectedCount).toBe(3)
64 | })
65 |
66 | it('array destructuring convenience', async () => {
67 | let count: () => number
68 |
69 | // Example: The following block scope exposes only the getter.
70 | {
71 | // The get and set functions are useful, for example, if you'd
72 | // like to expose only one or the other to external code, but
73 | // not both (making them write-only or read-only).
74 | const [get, set] = variable(0)
75 |
76 | // Expose the getter to the outside.
77 | count = get
78 |
79 | Promise.resolve().then(() => {
80 | set(1)
81 | set(count() + 1)
82 | set(3)
83 | })
84 | }
85 |
86 | // On the outside, we can only read the variable's value.
87 | let expectedCount = -1
88 | autorun(() => {
89 | expectedCount++
90 | expect(count()).toBe(expectedCount)
91 | })
92 |
93 | await Promise.resolve()
94 |
95 | expect(count()).toBe(3)
96 | expect(expectedCount).toBe(3)
97 | })
98 | })
99 |
100 | describe('circular()', () => {
101 | it('allows two variables to be synced to each other (two-way binding)', () => {
102 | const number = variable(0)
103 | const double = variable(0)
104 |
105 | let count = 0
106 |
107 | // Runs once initially.
108 | autorun(() => {
109 | count++
110 | number()
111 | double()
112 | })
113 |
114 | // This causes the previous autorun to run one more time when it syncs the vars.
115 | circular(
116 | number,
117 | () => number(double() / 2),
118 | double,
119 | () => double(number() * 2),
120 | )
121 |
122 | expect(count).toBe(2)
123 |
124 | // Causes the autorun to run two more times, because both variables
125 | // get modified.
126 | number(2)
127 | expect(count).toBe(4)
128 |
129 | expect(number()).toBe(2)
130 | expect(double()).toBe(4)
131 |
132 | // Causes the autorun to run two more times, because both variables
133 | // get modified.
134 | double(2)
135 | expect(count).toBe(6)
136 |
137 | expect(number()).toBe(1)
138 | expect(double()).toBe(2)
139 | })
140 | })
141 |
142 | describe('autorun()', () => {
143 | it('re-runs on changes of variables used within, and can be stopped from re-running', () => {
144 | expect(autorun).toBeInstanceOf(Function)
145 |
146 | const count = variable(0)
147 |
148 | let runCount = 0
149 |
150 | // Runs once immediately, then re-runs any time the variables used
151 | // within have changed (in this case, only the count() variable).
152 | const stop = autorun(() => {
153 | count()
154 | runCount++
155 | })
156 |
157 | count(1)
158 | count(2)
159 |
160 | expect(runCount).toBe(3)
161 |
162 | // Stops the autorun from re-running. It can now be garbage collected.
163 | stop()
164 |
165 | count(3)
166 | count(4)
167 |
168 | // The runCount is still the same because the autorun didn't re-run.
169 | expect(runCount).toBe(3)
170 | })
171 | })
172 |
173 | describe('@reactive and reactify', () => {
174 | it('is a function', () => {
175 | expect(reactive).toBeInstanceOf(Function)
176 | })
177 |
178 | it('does not prevent superclass constructor from receiving subclass constructor args', () => {
179 | @reactive
180 | class Insect {
181 | constructor(public result: number) {}
182 | }
183 |
184 | class Butterfly extends Insect {
185 | constructor(arg: number) {
186 | super(arg * 2)
187 | }
188 | }
189 |
190 | const b = new Butterfly(4)
191 |
192 | expect(b.result).toBe(8)
193 | })
194 |
195 | it('makes class properties reactive, using class and property/accessor decorators', () => {
196 | @reactive
197 | class Butterfly {
198 | @reactive colors = 3
199 | _wingSize = 2
200 |
201 | @reactive
202 | get wingSize() {
203 | return this._wingSize
204 | }
205 | set wingSize(s: number) {
206 | this._wingSize = s
207 | }
208 | }
209 |
210 | const b = new Butterfly()
211 |
212 | testButterflyProps(b)
213 | })
214 |
215 | it('show that reactify makes an infinite reactivity loop when used manually', () => {
216 | class Foo {
217 | amount = 3
218 |
219 | constructor() {
220 | reactify(this, ['amount'])
221 | }
222 | }
223 |
224 | class Bar extends Foo {
225 | double = 0
226 |
227 | constructor() {
228 | super()
229 | reactify(this, ['double'])
230 | this.double = this.amount * 2 // this tracks access of .amount
231 | }
232 | }
233 |
234 | let count = 0
235 |
236 | function loop() {
237 | autorun(() => {
238 | new Bar() // this reads and writes, causing an infinite loop
239 | count++
240 | })
241 | }
242 |
243 | count
244 |
245 | expect(loop).toThrowError(RangeError)
246 | expect(count).toBeGreaterThan(1)
247 | })
248 |
249 | it('show how to manually untrack constructors when not using decorators', () => {
250 | class Foo {
251 | amount = 3
252 |
253 | constructor() {
254 | reactify(this, ['amount'])
255 | }
256 | }
257 |
258 | class Bar extends Foo {
259 | double = 0
260 |
261 | constructor() {
262 | super()
263 | reactify(this, ['double'])
264 |
265 | untrack(() => {
266 | this.double = this.amount * 2
267 | })
268 | }
269 | }
270 |
271 | let count = 0
272 |
273 | function noLoop() {
274 | autorun(() => {
275 | new Bar() // this reads and writes, causing an infinite loop
276 | count++
277 | })
278 | }
279 |
280 | expect(noLoop).not.toThrow()
281 | expect(count).toBe(1)
282 | })
283 |
284 | it('automatically does not track reactivity in constructors when using decorators', () => {
285 | @reactive
286 | class Foo {
287 | @reactive amount = 3
288 | }
289 |
290 | @reactive
291 | class Bar extends Foo {
292 | @reactive double = 0
293 |
294 | constructor() {
295 | super()
296 | this.double = this.amount * 2 // this read of .amount should not be tracked
297 | }
298 | }
299 |
300 | let b: Bar
301 | let count = 0
302 |
303 | function noLoop() {
304 | autorun(() => {
305 | b = new Bar() // this should not track
306 | count++
307 | })
308 | }
309 |
310 | expect(noLoop).not.toThrow()
311 |
312 | const b2 = b!
313 |
314 | b!.amount = 4 // hence this should not trigger
315 |
316 | // If the effect ran only once initially, not when setting b.colors,
317 | // then both variables should reference the same instance
318 | expect(b!).toBe(b2)
319 | expect(count).toBe(1)
320 | })
321 |
322 | it('automatically does not track reactivity in constructors when using decorators even when not the root most decorator', () => {
323 | @reactive
324 | class Foo {
325 | @reactive amount = 3
326 | }
327 |
328 | function someOtherDecorator(Class: any) {
329 | console.log(Class)
330 | if (arguments.length === 1 && 'kind' in Class && Class.kind === 'class')
331 | return {...Class, finisher: (Klass: any) => class Foo extends Klass {}}
332 | return class Foo extends Class {}
333 | }
334 |
335 | @someOtherDecorator
336 | @reactive
337 | class Bar extends Foo {
338 | @reactive double = 0
339 |
340 | constructor() {
341 | super()
342 | this.double = this.amount * 2 // this read of .amount should not be tracked
343 | }
344 | }
345 |
346 | let b: Bar
347 | let count = 0
348 |
349 | function noLoop() {
350 | autorun(() => {
351 | b = new Bar() // this should not track
352 | count++
353 | })
354 | }
355 |
356 | expect(noLoop).not.toThrow()
357 |
358 | const b2 = b!
359 |
360 | b!.amount = 4 // hence this should not trigger
361 |
362 | // If the effect ran only once initially, not when setting b.colors,
363 | // then both variables should reference the same instance
364 | expect(b!).toBe(b2)
365 | expect(count).toBe(1)
366 | })
367 |
368 | it('makes class properties reactive, not using any decorators, specified in the constructor', () => {
369 | class Butterfly {
370 | colors = 3
371 | _wingSize = 2
372 |
373 | get wingSize() {
374 | return this._wingSize
375 | }
376 | set wingSize(s: number) {
377 | this._wingSize = s
378 | }
379 |
380 | constructor() {
381 | reactify(this, ['colors', 'wingSize'])
382 | }
383 | }
384 |
385 | const b = new Butterfly()
386 |
387 | testButterflyProps(b)
388 | })
389 |
390 | it('makes class properties reactive, with properties defined in the constructor', () => {
391 | class Butterfly {
392 | colors: number
393 | _wingSize: number
394 |
395 | get wingSize() {
396 | return this._wingSize
397 | }
398 | set wingSize(s: number) {
399 | this._wingSize = s
400 | }
401 |
402 | constructor() {
403 | this.colors = 3
404 | this._wingSize = 2
405 |
406 | reactify(this, ['colors', 'wingSize'])
407 | }
408 | }
409 |
410 | const b = new Butterfly()
411 |
412 | testButterflyProps(b)
413 | })
414 |
415 | it('makes class properties reactive, using only class decorator, specified via static prop', () => {
416 | @reactive
417 | class Butterfly {
418 | static reactiveProperties = ['colors', 'wingSize']
419 |
420 | colors = 3
421 | _wingSize = 2
422 |
423 | get wingSize() {
424 | return this._wingSize
425 | }
426 | set wingSize(s: number) {
427 | this._wingSize = s
428 | }
429 | }
430 |
431 | const b = new Butterfly()
432 | testButterflyProps(b)
433 | })
434 |
435 | it('makes class properties reactive, using only class decorator, specified via static prop, properties defined in the constructor', () => {
436 | @reactive
437 | class Butterfly {
438 | static reactiveProperties = ['colors', 'wingSize']
439 |
440 | colors: number
441 | _wingSize: number
442 |
443 | get wingSize() {
444 | return this._wingSize
445 | }
446 | set wingSize(s: number) {
447 | this._wingSize = s
448 | }
449 |
450 | constructor() {
451 | this.colors = 3
452 | this._wingSize = 2
453 | }
454 | }
455 |
456 | const b = new Butterfly()
457 | testButterflyProps(b)
458 | })
459 |
460 | it('makes class properties reactive, not using any decorators, specified via static prop', () => {
461 | class Butterfly {
462 | static reactiveProperties = ['colors', 'wingSize']
463 |
464 | colors = 3
465 | _wingSize = 2
466 |
467 | get wingSize() {
468 | return this._wingSize
469 | }
470 | set wingSize(s: number) {
471 | this._wingSize = s
472 | }
473 |
474 | constructor() {
475 | reactify(this, Butterfly)
476 | }
477 | }
478 |
479 | const b = new Butterfly()
480 | testButterflyProps(b)
481 | })
482 |
483 | it('makes class properties reactive, not using any decorators, specified via static prop, properties defined in the constructor', () => {
484 | class Butterfly {
485 | static reactiveProperties = ['colors', 'wingSize']
486 |
487 | colors: number
488 | _wingSize: number
489 |
490 | get wingSize() {
491 | return this._wingSize
492 | }
493 | set wingSize(s: number) {
494 | this._wingSize = s
495 | }
496 |
497 | constructor() {
498 | this.colors = 3
499 | this._wingSize = 2
500 |
501 | reactify(this, Butterfly)
502 | }
503 | }
504 |
505 | const b = new Butterfly()
506 | testButterflyProps(b)
507 | })
508 |
509 | it('can be used on a function-style class, with properties in the constructor', () => {
510 | function Butterfly() {
511 | // @ts-ignore
512 | this.colors = 3
513 | // @ts-ignore
514 | this._wingSize = 2
515 |
516 | // @ts-ignore
517 | reactify(this, Butterfly)
518 | }
519 |
520 | Butterfly.reactiveProperties = ['colors', 'wingSize']
521 |
522 | Butterfly.prototype = {
523 | get wingSize() {
524 | return this._wingSize
525 | },
526 | set wingSize(s: number) {
527 | this._wingSize = s
528 | },
529 | }
530 |
531 | // @ts-ignore
532 | const b = new Butterfly()
533 | testButterflyProps(b)
534 | })
535 |
536 | it('can be used on a function-style class, with properties on the prototype, reactify with static reactiveProperties in constructor', () => {
537 | function Butterfly() {
538 | // @ts-ignore
539 | reactify(this, Butterfly)
540 | }
541 |
542 | Butterfly.reactiveProperties = ['colors', 'wingSize']
543 |
544 | Butterfly.prototype = {
545 | colors: 3,
546 | _wingSize: 2,
547 |
548 | get wingSize() {
549 | return this._wingSize
550 | },
551 | set wingSize(s: number) {
552 | this._wingSize = s
553 | },
554 | }
555 |
556 | // @ts-ignore
557 | const b = new Butterfly()
558 | testButterflyProps(b)
559 | })
560 |
561 | it('can be used on a function-style class, with properties on the prototype, reactify with static reactiveProperties on the prototype', () => {
562 | function Butterfly() {}
563 |
564 | Butterfly.reactiveProperties = ['colors', 'wingSize']
565 |
566 | Butterfly.prototype = {
567 | colors: 3,
568 | _wingSize: 2,
569 |
570 | get wingSize() {
571 | return this._wingSize
572 | },
573 | set wingSize(s: number) {
574 | this._wingSize = s
575 | },
576 | }
577 |
578 | // @ts-ignore
579 | reactify(Butterfly.prototype, Butterfly)
580 |
581 | // @ts-ignore
582 | const b = new Butterfly()
583 | testButterflyProps(b)
584 | })
585 |
586 | it('can be used on a function-style class, with properties on the prototype, reactify with specific props in constructor', () => {
587 | function Butterfly() {
588 | // @ts-ignore
589 | reactify(this, ['colors', 'wingSize'])
590 | }
591 |
592 | Butterfly.prototype = {
593 | colors: 3,
594 | _wingSize: 2,
595 |
596 | get wingSize() {
597 | return this._wingSize
598 | },
599 | set wingSize(s: number) {
600 | this._wingSize = s
601 | },
602 | }
603 |
604 | // @ts-ignore
605 | const b = new Butterfly()
606 | testButterflyProps(b)
607 | })
608 |
609 | it('can be used on a function-style class, with properties on the prototype, reactify with specific props on the prototype', () => {
610 | function Butterfly() {}
611 |
612 | Butterfly.prototype = {
613 | colors: 3,
614 | _wingSize: 2,
615 |
616 | get wingSize() {
617 | return this._wingSize
618 | },
619 | set wingSize(s: number) {
620 | this._wingSize = s
621 | },
622 | }
623 |
624 | reactify(Butterfly.prototype, ['colors', 'wingSize'])
625 |
626 | // @ts-ignore
627 | const b = new Butterfly()
628 | testButterflyProps(b)
629 | })
630 |
631 | it('can be used on a function-style class, with properties in the constructor, reactive applied to constructor', () => {
632 | let Butterfly = function Butterfly() {
633 | // @ts-ignore
634 | this.colors = 3
635 | // @ts-ignore
636 | this._wingSize = 2
637 | }
638 |
639 | // @ts-ignore
640 | Butterfly.reactiveProperties = ['colors', 'wingSize']
641 |
642 | Butterfly.prototype = {
643 | get wingSize() {
644 | return this._wingSize
645 | },
646 | set wingSize(s: number) {
647 | this._wingSize = s
648 | },
649 | }
650 |
651 | Butterfly = reactive(Butterfly)
652 |
653 | // @ts-ignore
654 | const b = new Butterfly()
655 | testButterflyProps(b)
656 | })
657 |
658 | it('can be used on a function-style class, with properties on the prototype, reactive applied to constructor', () => {
659 | let Butterfly = function Butterfly() {}
660 |
661 | // @ts-ignore
662 | Butterfly.reactiveProperties = ['colors', 'wingSize']
663 |
664 | Butterfly.prototype = {
665 | colors: 3,
666 | _wingSize: 2,
667 |
668 | get wingSize() {
669 | return this._wingSize
670 | },
671 | set wingSize(s: number) {
672 | this._wingSize = s
673 | },
674 | }
675 |
676 | Butterfly = reactive(Butterfly)
677 |
678 | // @ts-ignore
679 | const b = new Butterfly()
680 | testButterflyProps(b)
681 | })
682 |
683 | it('can be used on a function-style class, with properties in the constructor, reactive applied to specific prototype properties', () => {
684 | let Butterfly = function Butterfly() {
685 | // @ts-ignore
686 | this.colors = 3
687 | // @ts-ignore
688 | this._wingSize = 2
689 | }
690 |
691 | // @ts-ignore
692 | Butterfly.reactiveProperties = ['colors', 'wingSize']
693 |
694 | Butterfly.prototype = {
695 | get wingSize() {
696 | return this._wingSize
697 | },
698 | set wingSize(s: number) {
699 | this._wingSize = s
700 | },
701 | }
702 |
703 | reactive(Butterfly.prototype, 'colors')
704 | reactive(Butterfly.prototype, 'wingSize')
705 | Butterfly = reactive(Butterfly)
706 |
707 | // @ts-ignore
708 | const b = new Butterfly()
709 | testButterflyProps(b)
710 | })
711 |
712 | it('can be used on a function-style class, with properties on the prototype, reactive applied to specific prototype properties', () => {
713 | let Butterfly = function Butterfly() {}
714 |
715 | // @ts-ignore
716 | Butterfly.reactiveProperties = ['colors', 'wingSize']
717 |
718 | Butterfly.prototype = {
719 | colors: 3,
720 | _wingSize: 2,
721 |
722 | get wingSize() {
723 | return this._wingSize
724 | },
725 | set wingSize(s: number) {
726 | this._wingSize = s
727 | },
728 | }
729 |
730 | reactive(Butterfly.prototype, 'colors')
731 | reactive(Butterfly.prototype, 'wingSize')
732 | Butterfly = reactive(Butterfly)
733 |
734 | // @ts-ignore
735 | const b = new Butterfly()
736 | testButterflyProps(b)
737 | })
738 | })
739 | })
740 |
741 | function testButterflyProps(b: {colors: number; wingSize: number; _wingSize: number}) {
742 | let count = 0
743 |
744 | autorun(() => {
745 | b.colors
746 | b.wingSize
747 | count++
748 | })
749 |
750 | expect(b.colors).toBe(3)
751 | expect(b.wingSize).toBe(2)
752 | expect(b._wingSize).toBe(2)
753 | expect(count).toBe(1)
754 |
755 | b.colors++
756 |
757 | expect(b.colors).toBe(4)
758 | expect(count).toBe(2)
759 |
760 | b.wingSize++
761 |
762 | expect(b.wingSize).toBe(3)
763 | expect(b._wingSize).toBe(3)
764 | expect(count).toBe(3)
765 | }
766 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import {getInheritedDescriptor} from 'lowclass'
2 | import {createSignal, createEffect, createRoot, untrack, getListener} from 'solid-js'
3 |
4 | export interface VariableGetter {
5 | (): T
6 | }
7 |
8 | export interface VariableSetter {
9 | (value: T): T
10 | }
11 |
12 | /** Represents a reactive variable. The value is set or gotten depending on passing an arg or no arg. */
13 | export interface Variable extends Iterable | VariableSetter> {
14 | /** Gets the variable value. */
15 | (value?: undefined): T
16 | /** Sets the variable value. */
17 | (value: T): T
18 | (value?: T): void | T
19 |
20 | get: VariableGetter
21 | set: VariableSetter
22 |
23 | // For array destructuring convenience
24 | [0]: VariableGetter
25 | [1]: VariableSetter
26 | [Symbol.iterator](): IterableIterator | VariableSetter>
27 | }
28 |
29 | function readVariable(this: Variable): T {
30 | return this()
31 | }
32 | function writeVariable(this: Variable, value: T): T {
33 | return this(value)
34 | }
35 |
36 | /**
37 | * Create a reactive variable.
38 | *
39 | * @example
40 | * let count = variable(0) // count starts at 0
41 | * count(1) // set the value of count to 1
42 | * count(count() + 1) // add 1
43 | * let currentValue = count() // read the current value
44 | * console.log(currentValue) // logs "2" to console
45 | */
46 | // eslint-disable-next-line typescript/explicit-function-return-type
47 | export function variable(value: T) {
48 | const [get, set] = createSignal(value, {equals: false})
49 |
50 | // FIXME, read arguments.length instead of detecting undefined values, because currently undefined value trigger a read, which means decoraators built on this treat `this.foo = undefined` as a read instead of a write.
51 | const variable = ((value?: T) => {
52 | if (typeof value === 'undefined') return get()
53 | set(() => value)
54 | return value
55 | }) as Variable
56 |
57 | // WTF TypeScript, why do I need `any` here.
58 | const getter = readVariable.bind(variable as any) as VariableGetter
59 | const setter = writeVariable.bind(variable as any) as VariableSetter
60 |
61 | // For object destructuring convenience.
62 | variable.get = getter
63 | variable.set = setter
64 |
65 | // For array destructuring convenience.
66 | variable[0] = getter
67 | variable[1] = setter
68 | variable[Symbol.iterator] = function* () {
69 | yield variable[0]
70 | yield variable[1]
71 | }
72 |
73 | return variable as [VariableGetter, VariableSetter] & Variable
74 | }
75 |
76 | export type Computation = (previousValue?: unknown) => unknown
77 | export type StopFunction = () => void
78 |
79 | /**
80 | * Automatically run a "computation" when any reactive variable used inside the
81 | * computation has changed. The "computation" is a function passed into
82 | * autorun().
83 | *
84 | * @param {Computation} f - A "computation" to re-run when any of the reactive
85 | * variables used inside of it change.
86 | * @return {StopFunction} - Returns a function that can be called to explicitly
87 | * stop the computation from running, allowing it to be garbage collected.
88 | */
89 | // TODO Option for autorun() to batch updates into a single update in the next microtask.
90 | // TODO Option for autorun() to skip the first run.
91 | // TODO Option for autorun() to provide which properties caused the re-run.
92 | export function autorun(f: Computation): StopFunction {
93 | let stop: StopFunction
94 |
95 | createRoot(dispose => {
96 | stop = dispose
97 | createEffect(f)
98 | })
99 |
100 | return stop!
101 | }
102 |
103 | export function reactive(protoOrClassElement: any, propName?: string, _descriptor?: PropertyDescriptor): any {
104 | // If used as a newer Babel decorator
105 | const isDecoratorV2 = arguments.length === 1 && 'kind' in protoOrClassElement
106 | if (isDecoratorV2) {
107 | const classElement = protoOrClassElement
108 |
109 | // If used as a class decorator.
110 | if (classElement.kind === 'class') return {...classElement, finisher: reactiveClassFinisher}
111 |
112 | // If used as a property or accessor decorator (@reactive isn't intended for
113 | // methods).
114 | return {
115 | ...classElement,
116 | finisher(Class: AnyClassWithReactiveProps) {
117 | _trackReactiveProperty(Class, classElement.key)
118 |
119 | return classElement.finisher?.(Class) ?? Class
120 | },
121 | }
122 | }
123 |
124 | // Used as a v1 legacy decorator.
125 |
126 | // If used as a class decorator.
127 | if (arguments.length === 1 && typeof protoOrClassElement === 'function') {
128 | const Class = protoOrClassElement
129 | return reactiveClassFinisher(Class)
130 | }
131 |
132 | // If used as a property or accessor decorator (this isn't intended for
133 | // methods).
134 | const Class = protoOrClassElement.constructor
135 | _trackReactiveProperty(Class, propName!)
136 | }
137 |
138 | export function _trackReactiveProperty(Class: AnyClassWithReactiveProps, propName: string) {
139 | if (!Class.reactiveProperties || !Class.hasOwnProperty('reactiveProperties')) Class.reactiveProperties = []
140 | if (!Class.reactiveProperties.includes(propName)) Class.reactiveProperties.push(propName)
141 | }
142 |
143 | function reactiveClassFinisher(Class: AnyClassWithReactiveProps) {
144 | if (Class.hasOwnProperty('__isReactive__')) return Class
145 |
146 | return class ReactiveDecoratorFinisher extends Class {
147 | // This is a flag that other decorators can check, f.e. lume/elements @element decorator.
148 | static __isReactive__: true = true
149 |
150 | constructor(...args: any[]) {
151 | if (getListener()) {
152 | return untrack(() => {
153 | const self = Reflect.construct(Class, args, new.target) // super()
154 | reactify(self, Class)
155 | return self
156 | })
157 | }
158 |
159 | super(...args)
160 | reactify(this, Class)
161 | }
162 | }
163 | }
164 |
165 | function _reactive(obj: ObjWithReactifiedProps, propName: PropertyKey): void {
166 | if (typeof propName !== 'string') throw new Error('TODO: support for non-string fields with @reactive decorator')
167 |
168 | const vName = 'v_' + propName
169 |
170 | // XXX If obj already has vName, skip making an accessor? I think perhaps
171 | // not, because a subclass might override a property so it is not reactive,
172 | // and a further subclass might want to make it reactive again in which
173 | // case returning early would cause the subclass subclass's property not to
174 | // be reactive.
175 | // if (obj[vName] !== undefined) return
176 |
177 | let descriptor: PropertyDescriptor | undefined = getInheritedDescriptor(obj, propName)
178 |
179 | let originalGet: (() => any) | undefined
180 | let originalSet: ((v: any) => void) | undefined
181 | let initialValue: unknown
182 |
183 | // TODO if there is an inherited accessor, we need to ensure we still call
184 | // it so that we're extending instead of overriding. Otherwise placing
185 | // @reactive on a property will break that functionality in those cases.
186 | //
187 | // Right now, originalGet will only be called if it is on the current
188 | // prototype, but we aren't checking for any accessor that may be inherited.
189 |
190 | if (descriptor) {
191 | originalGet = descriptor.get
192 | originalSet = descriptor.set
193 |
194 | if (originalGet || originalSet) {
195 | // reactivity requires both
196 | if (!originalGet || !originalSet) {
197 | console.warn(
198 | 'The `@reactive` decorator was used on an accessor named "' +
199 | propName +
200 | '" which had a getter or a setter, but not both. Reactivity on accessors works only when accessors have both get and set. In this case the decorator does not do anything.',
201 | )
202 | return
203 | }
204 |
205 | delete descriptor.get
206 | delete descriptor.set
207 | } else {
208 | initialValue = descriptor.value
209 |
210 | // if it isn't writable, we don't need to make a reactive variable because
211 | // the value won't change
212 | if (!descriptor.writable) {
213 | console.warn(
214 | 'The `@reactive` decorator was used on a property named ' +
215 | propName +
216 | ' that is not writable. Reactivity is not enabled for non-writable properties.',
217 | )
218 | return
219 | }
220 |
221 | delete descriptor.value
222 | delete descriptor.writable
223 | }
224 | }
225 |
226 | descriptor = {
227 | configurable: true,
228 | enumerable: true,
229 | ...descriptor,
230 | get: originalGet
231 | ? function (this: any): unknown {
232 | // track reactivity, but get the value from the original getter
233 |
234 | // XXX this causes initialValue to be held onto even if the original
235 | // prototype value has changed. In pratice the original prototype
236 | // values usually never change, and these days people don't normally
237 | // use prototype values to begin with.
238 | const v = __getReactiveVar(this, vName, initialValue)
239 | v()
240 |
241 | return originalGet!.call(this)
242 | }
243 | : function (this: any): unknown {
244 | const v = __getReactiveVar(this, vName, initialValue)
245 | return v()
246 | },
247 | set: originalSet
248 | ? function (this: any, newValue: unknown) {
249 | originalSet!.call(this, newValue)
250 |
251 | const v = __getReactiveVar(this, vName)
252 | v(newValue)
253 |
254 | // __propsSetAtLeastOnce__ is a Set that tracks which reactive
255 | // properties have been set at least once. @lume/element uses this
256 | // to detect if a reactive prop has been set, and if so will not
257 | // overwrite the value with any value from custom element
258 | // pre-upgrade.
259 | if (!this.__propsSetAtLeastOnce__) this.__propsSetAtLeastOnce__ = new Set()
260 | this.__propsSetAtLeastOnce__.add(propName)
261 | }
262 | : function (this: any, newValue: unknown) {
263 | const v = __getReactiveVar(this, vName)
264 | v(newValue)
265 |
266 | if (!this.__propsSetAtLeastOnce__) this.__propsSetAtLeastOnce__ = new Set()
267 | this.__propsSetAtLeastOnce__.add(propName)
268 | },
269 | }
270 |
271 | if (!obj.__reactifiedProps__) obj.__reactifiedProps__ = new Set()
272 | obj.__reactifiedProps__.add(propName)
273 |
274 | Object.defineProperty(obj, propName, descriptor)
275 | }
276 |
277 | function __getReactiveVar(instance: Obj>, vName: string, initialValue: T = undefined!): Variable {
278 | // NOTE alternatively, we could use a WeakMap instead of exposing the
279 | // variable on the instance. We could also use Symbols keys for
280 | // semi-privacy.
281 | let v: Variable = instance[vName]
282 |
283 | if (v) return v
284 |
285 | instance[vName] = v = variable(initialValue)
286 |
287 | return v
288 | }
289 |
290 | type AnyClass = new (...args: any[]) => object
291 | type AnyClassWithReactiveProps = (new (...args: any[]) => object) & {
292 | reactiveProperties?: string[]
293 | __isReactive__?: true
294 | }
295 |
296 | // Define (or unshadow) reactive accessors on obj, which is generally `this`
297 | // inside of a constructor (this is what the documentation prescribes).
298 | export function reactify(obj: T, props: (keyof T)[]): typeof obj
299 | export function reactify(obj: InstanceType, ctor: C): typeof obj
300 | export function reactify(obj: Obj, propsOrClass: PropertyKey[] | AnyClassWithReactiveProps) {
301 | if (isClass(propsOrClass)) {
302 | const Class = propsOrClass
303 |
304 | // let props = classReactiveProps.get(Class)
305 | // if (props) unshadowReactiveAccessors(obj, props)
306 | // props = Class.reactiveProperties
307 |
308 | const props = Class.reactiveProperties
309 | if (Array.isArray(props)) createReactiveAccessors(obj, props)
310 | } else {
311 | const props = propsOrClass
312 | createReactiveAccessors(obj, props)
313 | }
314 |
315 | return obj
316 | }
317 |
318 | function isClass(obj: unknown): obj is AnyClass {
319 | return typeof obj == 'function'
320 | }
321 |
322 | // Defines a reactive accessor on obj.
323 | function createReactiveAccessors(obj: ObjWithReactifiedProps, props: PropertyKey[]) {
324 | for (const prop of props) {
325 | if (obj.__reactifiedProps__?.has(prop)) continue
326 |
327 | const initialValue = obj[prop]
328 | _reactive(obj, prop)
329 | obj[prop] = initialValue
330 | }
331 | }
332 |
333 | type Obj = Record & {constructor: AnyClass}
334 | type ObjWithReactifiedProps = Obj & {__reactifiedProps__?: Set}
335 |
336 | /**
337 | * Allow two reactive variables to depend on each other's values, without
338 | * causing an infinite loop.
339 | */
340 | export function circular(
341 | first: VariableGetter,
342 | setFirst: (v: Type) => void,
343 | second: VariableGetter,
344 | setSecond: (v: Type) => void,
345 | ): StopFunction {
346 | let initial = true
347 |
348 | const stop1 = autorun(() => {
349 | const v = first()
350 | if (initial && !(initial = false)) setSecond(v)
351 | else initial = true
352 | })
353 |
354 | const stop2 = autorun(() => {
355 | const v = second()
356 | if (initial && !(initial = false)) setFirst(v)
357 | else initial = true
358 | })
359 |
360 | return function stop() {
361 | stop1()
362 | stop2()
363 | }
364 | }
365 |
366 | export const version = '0.10.1'
367 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/@lume/cli/config/ts.config.json",
3 | "compilerOptions": {
4 | // legacy decorators until we switch to classy-solid and standard (non-legacy) decorators
5 | "experimentalDecorators": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------