├── .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 | --------------------------------------------------------------------------------