-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 17c4d3d
Showing
15 changed files
with
3,211 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
name: Test | ||
on: | ||
push: | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-20.04 | ||
|
||
strategy: | ||
matrix: | ||
node-version: [ 16.x, 18.x ] | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- name: Get yarn cache directory path | ||
id: yarn-cache-dir-path | ||
run: echo "::set-output name=dir::$(yarn cache dir)" | ||
|
||
- uses: actions/cache@v2 | ||
with: | ||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | ||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} | ||
restore-keys: | | ||
${{ runner.os }}-yarn- | ||
- name: Use Node.js ${{ matrix.node-version }} | ||
uses: actions/setup-node@v2 | ||
with: | ||
node-version: ${{ matrix.node-version }} | ||
|
||
- run: yarn install --frozen-lockfile | ||
|
||
- run: yarn test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
lib | ||
es |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"singleQuote": true, | ||
"bracketSpacing": false, | ||
"trailingComma": "all", | ||
"proseWrap": "always", | ||
"arrowParens": "avoid", | ||
"endOfLine": "auto" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2024 Deeplay | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# abort-controller-x-reactive-store [![npm version][npm-image]][npm-url] | ||
|
||
Reactive store primitive and helpers. | ||
|
||
This is a companion package of | ||
[`abort-controller-x`](https://github.com/deeplay-io/abort-controller-x). | ||
|
||
- [Installation](#installation) | ||
- [API](#api) | ||
- [`Store`](#store) | ||
- [`deriveStore`](#derivestore) | ||
- [`watchStore`](#watchstore) | ||
- [Usage with React](#usage-with-react) | ||
|
||
## Installation | ||
|
||
``` | ||
yarn add abort-controller-x-reactive-store | ||
``` | ||
|
||
## API | ||
|
||
### `Store` | ||
|
||
```ts | ||
type Store<T> = { | ||
value: T; | ||
wait(signal: AbortSignal, condition: (value: T) => boolean): Promise<T>; | ||
}; | ||
|
||
type ReadonlyStore<T> = Readonly<Store<T>>; | ||
``` | ||
|
||
A reactive store (a.k.a. reactive variable) holds a value that can be read and | ||
updated, and can also be observed by means of waiting for a condition to be met. | ||
|
||
The `wait` method returns a promise that resolves when the condition is met, or | ||
rejects with an `AbortError` if the signal is aborted. | ||
|
||
### `deriveStore` | ||
|
||
```ts | ||
function deriveStore<T, R>( | ||
parentStore: ReadonlyStore<T>, | ||
transform: (value: T) => R, | ||
): Store<R>; | ||
``` | ||
|
||
Derives a new store from a parent store by applying a transformation function to | ||
its value. | ||
|
||
### `watchStore` | ||
|
||
```ts | ||
async function watchStore<T>( | ||
signal: AbortSignal, | ||
store: ReadonlyStore<T>, | ||
): AsyncIterable<T>; | ||
``` | ||
|
||
Allows to react on changes of a store value using async iteration, e.g.: | ||
|
||
```ts | ||
for await (const value of watchStore(signal, store)) { | ||
console.log(value); | ||
} | ||
``` | ||
|
||
Note that it is not guaranteed that every assignment to the store value will be | ||
logged. For example, in case of multiple synchronous assignments, some of them | ||
may be skipped due to the async nature of promises. However, it is always | ||
guaranteed that the last value will be logged. | ||
|
||
## Usage with React | ||
|
||
You can use the following hook to bind to a store in a React component: | ||
|
||
```ts | ||
import {run} from 'abort-controller-x'; | ||
import {ReadonlyStore, watchStore} from 'abort-controller-x-reactive-store'; | ||
import {useEffect, useState} from 'react'; | ||
|
||
function useStoreValue<T>(store: ReadonlyStore<T>): T { | ||
const [value, setValue] = useState(store.value); | ||
|
||
useEffect(() => { | ||
const stop = run(async signal => { | ||
for await (const value of watchStore(signal, store)) { | ||
setState(value); | ||
} | ||
}); | ||
|
||
return () => { | ||
stop(); | ||
}; | ||
}, [store]); | ||
|
||
return value; | ||
} | ||
``` | ||
|
||
[npm-image]: https://badge.fury.io/js/abort-controller-x-reactive-store.svg | ||
[npm-url]: https://badge.fury.io/js/abort-controller-x-reactive-store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'node', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "abort-controller-x-reactive-store", | ||
"version": "0.0.0", | ||
"description": "Reactive store primitive and helpers", | ||
"keywords": [ | ||
"abort", | ||
"abortable", | ||
"abort-controller", | ||
"reactive", | ||
"reactive-store", | ||
"reactive-variable" | ||
], | ||
"repository": "deeplay-io/abort-controller-x-reactive-store", | ||
"sideEffects": false, | ||
"main": "lib/index.js", | ||
"module": "es/index.js", | ||
"typings": "lib/index.d.ts", | ||
"files": [ | ||
"src", | ||
"lib", | ||
"es" | ||
], | ||
"scripts": { | ||
"clean": "rimraf lib es", | ||
"test": "jest --passWithNoTests", | ||
"build:lib": "tsc -P tsconfig.build.json", | ||
"build:es": "tsc -P tsconfig.es.json", | ||
"build": "npm run build:lib && npm run build:es", | ||
"prepublishOnly": "npm test && npm run clean && npm run build" | ||
}, | ||
"author": "Daniel Lytkin <[email protected]>", | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@types/jest": "^26.0.23", | ||
"jest": "^27.0.4", | ||
"prettier": "^2.1.2", | ||
"rimraf": "^2.6.3", | ||
"ts-jest": "^27.0.3", | ||
"typescript": "^4.3.2" | ||
}, | ||
"dependencies": { | ||
"abort-controller-x": "^0.4.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import {execute, throwIfAborted} from 'abort-controller-x'; | ||
|
||
export type Store<T> = { | ||
value: T; | ||
wait<S extends T>( | ||
signal: AbortSignal, | ||
condition: (value: T) => value is S, | ||
): Promise<S>; | ||
wait(signal: AbortSignal, condition: (value: T) => boolean): Promise<T>; | ||
}; | ||
|
||
export type ReadonlyStore<T> = Readonly<Store<T>>; | ||
|
||
export function Store<T>(initialValue: T): Store<T> { | ||
let value = initialValue; | ||
|
||
const listeners = new Set<(value: T) => void>(); | ||
|
||
return { | ||
get value() { | ||
return value; | ||
}, | ||
set value(nextValue: T) { | ||
if (value === nextValue) { | ||
return; | ||
} | ||
|
||
value = nextValue; | ||
|
||
for (const listener of listeners) { | ||
listener(value); | ||
} | ||
}, | ||
|
||
wait<S extends T>( | ||
signal: AbortSignal, | ||
condition: (value: T) => value is S, | ||
) { | ||
throwIfAborted(signal); | ||
|
||
if (condition(value)) { | ||
return Promise.resolve(value); | ||
} | ||
|
||
return execute<S>(signal, resolve => { | ||
const listener = (value: T) => { | ||
if (condition(value)) { | ||
resolve(value); | ||
listeners.delete(listener); | ||
} | ||
}; | ||
|
||
listeners.add(listener); | ||
|
||
return () => { | ||
listeners.delete(listener); | ||
}; | ||
}); | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import {ReadonlyStore} from './Store'; | ||
|
||
export function deriveStore<T, R>( | ||
parentStore: ReadonlyStore<T>, | ||
transform: (value: T) => R, | ||
): ReadonlyStore<R> { | ||
let cache: {parentValue: T; transformedValue: R} | undefined; | ||
|
||
function memoizedTransform(parentValue: T): R { | ||
if (cache && cache.parentValue === parentValue) { | ||
return cache.transformedValue; | ||
} | ||
|
||
const transformedValue = transform(parentValue); | ||
|
||
cache = {parentValue, transformedValue}; | ||
|
||
return transformedValue; | ||
} | ||
|
||
return { | ||
get value() { | ||
return memoizedTransform(parentStore.value); | ||
}, | ||
|
||
async wait<S extends R>( | ||
signal: AbortSignal, | ||
condition: (value: R) => value is S, | ||
): Promise<S> { | ||
const parentValue = await parentStore.wait(signal, parentValue => | ||
condition(memoizedTransform(parentValue)), | ||
); | ||
|
||
return memoizedTransform(parentValue) as S; | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './Store'; | ||
export * from './deriveStore'; | ||
export * from './watchStore'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {ReadonlyStore} from './Store'; | ||
|
||
export async function* watchStore<T>( | ||
signal: AbortSignal, | ||
store: ReadonlyStore<T>, | ||
): AsyncIterable<T> { | ||
let lastValue = store.value; | ||
|
||
yield lastValue; | ||
|
||
while (true) { | ||
const value = await store.wait(signal, value => value !== lastValue); | ||
|
||
yield value; | ||
|
||
lastValue = value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"extends": "./tsconfig", | ||
"exclude": [ | ||
"src/**/*.test.ts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"extends": "./tsconfig", | ||
"compilerOptions": { | ||
"module": "es6", | ||
"outDir": "es" | ||
}, | ||
"exclude": [ | ||
"src/**/*.test.ts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ES2019", | ||
"lib": ["ES2019", "ES2020.Promise", "DOM"], | ||
"module": "commonjs", | ||
"moduleResolution": "node", | ||
"outDir": "lib", | ||
"sourceMap": true, | ||
"declaration": true, | ||
"strict": true, | ||
"noImplicitReturns": true, | ||
"noUnusedLocals": true | ||
}, | ||
"files": ["src/index.ts"], | ||
"include": ["src/**/*.test.ts"] | ||
} |
Oops, something went wrong.