├── .gitignore
├── README.md
├── demo
├── .npmignore
├── Calculate.tsx
├── Clock.tsx
├── WithWorker.tsx
├── WithoutWorker.tsx
├── index.html
├── index.tsx
├── lib
│ └── primes.ts
├── package.json
├── redux
│ ├── actions.ts
│ ├── reducer.ts
│ ├── store.ts
│ └── worker.ts
├── tsconfig.json
└── yarn.lock
├── img
├── react-redux-worker.svg
└── worker-demo.gif
├── package.json
├── src
├── ProxyStore.ts
├── StateContext.tsx
├── StoreContext.tsx
├── createProxyStore.ts
├── expose.ts
├── getProvider.tsx
├── hooks.ts
├── index.ts
└── uniqueId.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | .rts2_cache_cjs
6 | .rts2_cache_esm
7 | .rts2_cache_umd
8 | dist
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-redux-worker
2 |
3 | Run a Redux store in a web worker.
4 |
5 | ## Why?
6 |
7 | If you're doing any sort of computationally expensive work in your [Redux](https://redux.js.org) reducers or middleware, it
8 | can prevent your UI from responding while it's thinking—making your application feel slow and
9 | unresponsive.
10 |
11 | In theory, web workers should be the perfect solution: You can do your heavy lifting in a worker
12 | thread without interfering with your main UI thread. But the message-based [web worker
13 | API](https://redux.js.org) puts us on unfamiliar terrain.
14 |
15 | This library is intended to make the developer experience of using a worker-based Redux store as
16 | similar as possible to an ordinary Redux setup.
17 |
18 | ## How it works
19 |
20 | This library provides you with a **proxy Redux store**. To your application, the proxy looks just
21 | like the real thing: You communicate with it synchronously using `useDispatch` and `useSelector`
22 | hooks just like the ones that the official [react-redux](https://github.com/reduxjs/react-redux)
23 | bindings provide.
24 |
25 | 
26 |
27 | The proxy then handles messaging back and forth with the store in the worker using the
28 | [Comlink](https://github.com/GoogleChromeLabs/comlink) library, built by the Google Chrome team.
29 |
30 | ## Running the demo
31 |
32 | ```bash
33 | yarn
34 | yarn start
35 | ```
36 |
37 | Then open http://localhost:1234 in a browser. You should see something like this:
38 |
39 | 
40 |
41 | ## Usage
42 |
43 | ### Add the dependency
44 |
45 | ```bash
46 | yarn add react-redux-worker
47 | ```
48 |
49 | ### Put your store in a worker, and create a proxy
50 |
51 | In a stand-alone file called `worker.ts` or `store.worker.ts`, import your reducer (and middlewares,
52 | if applicable) and build your store the way you always have. Then wrap it in a proxy store,
53 | and expose that as a worker messaging endpoint:
54 |
55 | ```ts
56 | // worker.ts
57 | import { createStore } from 'redux'
58 | import { reducer } from './reducer'
59 | import { expose, createProxyStore } from 'react-redux-worker'
60 |
61 | const store = createStore(reducer) // if you have initial state and/or middleware you can add them here as well
62 | const proxyStore = createProxyStore(store)
63 | expose(proxyStore, self)
64 | ```
65 |
66 | ### Add a context provider for the proxy store
67 |
68 | At the root of your app, replace your standard react-redux `Provider` with one that gives access to
69 | the proxy store.
70 |
71 | ```tsx
72 | import { getProvider } from 'react-redux-worker'
73 |
74 | const worker = new Worker('./redux/worker.ts')
75 | const ProxyProvider = getProvider(worker)
76 |
77 | ReactDOM.render(
78 |
79 |
80 | ,
81 | document.querySelector('.root')
82 | )
83 | ```
84 |
85 | ### Use the proxy `useDispatch` and `useSelector` hooks in your components
86 |
87 | ```tsx
88 | import * as React from 'react'
89 | import { useDispatch, useSelector } from 'react-redux-worker'
90 | import { actions } from './redux/actions'
91 |
92 | export function WithWorker() {
93 | const state = useSelector((s => s)
94 | const dispatch = useDispatch()
95 |
96 | dispatch(actions.setBusy(true))
97 | dispatch(actions.doSomeHeavyLifting())
98 | dispatch(actions.setBusy(false))
99 |
100 | return (
101 | {state.busy ? (
102 | Thinking...
103 | ) : (
104 | Result: {state.result}
105 | )}
106 |
)
107 | }
108 | ```
109 |
110 | ## Prior art
111 |
112 | - Based on [redux-workerized](https://github.com/mizchi/redux-workerized) by
113 | [@mizchi](https://github.com/mizchi/)
114 | - Uses some ideas from [A Guide to using Web Workers in
115 | React](https://www.fullstackreact.com/articles/introduction-to-web-workers-with-react) by [@yomieluwande](https://twitter.com/yomieluwande)
116 |
--------------------------------------------------------------------------------
/demo/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | dist
--------------------------------------------------------------------------------
/demo/Calculate.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { actions } from './redux/actions'
3 | import { State } from './redux/reducer'
4 | import { Dispatch } from 'redux'
5 |
6 | // When doing calculations in UI thread, React won't update at all if we don't do this
7 | export const nextFrame = () => new Promise(ok => requestAnimationFrame(ok))
8 |
9 | export const Calculate = ({
10 | state,
11 | dispatch,
12 | }: {
13 | state: State
14 | dispatch: Dispatch
15 | }) => {
16 | const calculate = async () => {
17 | dispatch(actions.setBusy(true))
18 | await nextFrame()
19 | for (let i = 0; i < 10; i++) {
20 | dispatch(actions.nextPrime())
21 | await nextFrame()
22 | }
23 | dispatch(actions.setBusy(false))
24 | }
25 | const style = {
26 | button: {
27 | border: '2px solid',
28 | margin: '10px 0',
29 | padding: '10px 30px',
30 | borderRadius: '10px',
31 | cursor: 'pointer',
32 | outline: 'none',
33 | minWidth: '10em',
34 | ...(state.busy
35 | ? {
36 | background: '#008080bb',
37 | borderColor: '#008080',
38 | color: 'white',
39 | }
40 | : {
41 | background: '#00808022',
42 | borderColor: '#008080',
43 | color: '#008080',
44 | }),
45 | },
46 | }
47 | return (
48 |
49 |
50 | {state.busy ? 'Calculating...' : 'Calculate'}
51 |
52 | {state.primes.map(p => (
53 |
{p}
54 | ))}
55 |
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/demo/Clock.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { useState } from 'react'
3 | import ReactCountdownClock from 'react-countdown-clock'
4 |
5 | export const Clock = () => {
6 | const [time, setTime] = useState(3)
7 | const [seconds, setSeconds] = useState(time)
8 |
9 | const restart = () => {
10 | // can't just set seconds back to the starting value, or it won't restart
11 | const t = time + 0.00000000001
12 | setTime(t)
13 | setSeconds(t)
14 | }
15 |
16 | return (
17 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/demo/WithWorker.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { useDispatch, useSelector } from '../src'
3 | import { State } from './redux/reducer'
4 | import { Calculate } from './Calculate'
5 |
6 | export function WithWorker() {
7 | const state = useSelector((s: State) => s)
8 | const dispatch = useDispatch()
9 |
10 | return (
11 |
12 |
With worker
13 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/demo/WithoutWorker.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector, useDispatch } from 'react-redux'
3 | import { Calculate } from './Calculate'
4 | import { State } from './redux/reducer'
5 |
6 | export function WithoutWorker() {
7 | const state = useSelector((s: State) => s)
8 | const dispatch = useDispatch()
9 |
10 | return (
11 |
12 |
Without worker
13 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/demo/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import * as ReactDOM from 'react-dom'
3 | import { Provider } from 'react-redux'
4 | import { store } from './redux/store'
5 |
6 | import { getProvider } from '../src' // replace with 'react-redux-worker'
7 |
8 | import { Clock } from './Clock'
9 | import { WithoutWorker } from './WithoutWorker'
10 | import { WithWorker } from './WithWorker'
11 |
12 | // Set up proxy provider
13 | const worker = new Worker('./redux/worker.ts')
14 | const ProxyProvider = getProvider(worker)
15 |
16 | // Set up regular provider
17 | const RegularProvider = ({ children }) => (
18 | {children}
19 | )
20 |
21 | ReactDOM.render(
22 |
23 |
24 |
Use the buttons to find a few large prime numbers.
25 |
26 | Without a worker, notice how the animation stops updating every time the
27 | app calculates a new prime.
28 |
29 |
30 | {/* using redux-workerized */}
31 |
32 |
33 |
34 |
35 | {/* using regular redux */}
36 |
37 |
38 |
39 |
40 |
,
41 | document.querySelector('.root')
42 | )
43 |
--------------------------------------------------------------------------------
/demo/lib/primes.ts:
--------------------------------------------------------------------------------
1 | export const knownPrimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
2 | export const highestKnownPrime = () => knownPrimes[knownPrimes.length - 1]
3 |
4 | // returns an array of primes lower than `max`
5 | export const primes = (max = 100) => {
6 | let p = highestKnownPrime()
7 | while (p < max) {
8 | p = nextPrime(p)
9 | if (p > highestKnownPrime()) knownPrimes.push(p)
10 | }
11 | return knownPrimes.filter(p => p < max)
12 | }
13 |
14 | // returns the next prime after `n`
15 | export const nextPrime = (n: number): number => {
16 | // if it's in the range of the list, just look it up
17 | if (n < highestKnownPrime()) return knownPrimes.find(p => p > n) as number
18 |
19 | // brute-force time
20 | let candidates = candidateGenerator(n)
21 | let candidate
22 | do candidate = candidates.next().value
23 | while (!isPrime(candidate))
24 |
25 | return candidate
26 | }
27 |
28 | export const nthPrime = (n: number): number => {
29 | let i = 1
30 | let p = 2 // 2 is the first prime
31 | while (i++ < n) p = nextPrime(p)
32 | return p
33 | }
34 |
35 | // uses the fact that every prime over 30 is in one of the forms
36 | // 30k ± 1, 30k ± 7, 30k ± 11, 30k ± 13
37 | // This allows us to eliminate more than half (11/15 = 73%) of the search space
38 | export const candidateGenerator = function*(n: number) {
39 | const B = 30
40 | const D = [-13, -11, -7, -1, 1, 7, 11, 13]
41 | let i = 0
42 | let base = Math.trunc(n / B) * B
43 | while (true) {
44 | let candidate = base + D[i]
45 | if (candidate > n) yield candidate
46 | i += 1
47 | if (i >= D.length) {
48 | base += B
49 | i = 0
50 | }
51 | }
52 | }
53 |
54 | // returns true if a number is prime, false if it is composite
55 | export const isPrime = (n: number) => {
56 | // negative numbers and zero are not prime
57 | if (n < 1) return false
58 |
59 | // if it's in the range of the list, then it's only prime if it's on the list
60 | const hnp = highestKnownPrime()
61 | if (n <= hnp) return knownPrimes.includes(n)
62 |
63 | const sqrt = Math.sqrt(n)
64 |
65 | // if it's divisible by a number on the list, it's not prime
66 | if (knownPrimes.find(p => n % p === 0)) return false
67 | // if it's not divisible by a number on the list and it's
68 | // smaller than the square of the largest known prime, then it's prime
69 | else if (sqrt < hnp) return true
70 |
71 | // Brute-force time
72 | let candidate = hnp
73 | let candidates = candidateGenerator(candidate)
74 | do {
75 | if (n % candidate === 0) return false
76 | candidate = candidates.next().value
77 | } while (candidate <= sqrt)
78 |
79 | // must be prime
80 | return true
81 | }
82 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "parcel index.html",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "react-countdown-clock": "2",
12 | "react-redux": "7",
13 | "redux": "4"
14 | },
15 | "devDependencies": {
16 | "@types/react": "16",
17 | "@types/react-dom": "16",
18 | "@types/react-redux": "^7.1.4",
19 | "parcel": "1",
20 | "typescript": "3"
21 | },
22 | "alias": {
23 | "react": "../node_modules/react",
24 | "react-dom": "../node_modules/react-dom/profiling",
25 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/demo/redux/actions.ts:
--------------------------------------------------------------------------------
1 | import { AnyAction } from 'redux'
2 |
3 | // Constants
4 |
5 | export const NEXT_PRIME = 'counter/next-prime'
6 | export const SET_BUSY = 'counter/set-busy'
7 |
8 | export interface Action extends AnyAction {}
9 |
10 | export const actions: { [key: string]: (...args: any[]) => Action } = {
11 | nextPrime: () => ({ type: NEXT_PRIME }),
12 | setBusy: (value: boolean) => ({ type: SET_BUSY, payload: { value } }),
13 | }
14 |
--------------------------------------------------------------------------------
/demo/redux/reducer.ts:
--------------------------------------------------------------------------------
1 | import { AnyAction } from 'redux'
2 | import { NEXT_PRIME, SET_BUSY } from './actions'
3 | import { nextPrime } from '../lib/primes'
4 |
5 | export type State = {
6 | prime: number
7 | primes: number[]
8 | busy: boolean
9 | }
10 |
11 | const initialState = {
12 | prime: 861504408610801,
13 | primes: [],
14 | busy: false,
15 | }
16 |
17 | function reducer(state: State = initialState, action: AnyAction) {
18 | switch (action.type) {
19 | case NEXT_PRIME: {
20 | const next = nextPrime(state.prime)
21 | return {
22 | ...state,
23 | prime: next,
24 | primes: [...state.primes, next],
25 | }
26 | }
27 | case SET_BUSY: {
28 | const { value } = action.payload
29 | return {
30 | ...state,
31 | busy: value,
32 | }
33 | }
34 | default:
35 | return state
36 | }
37 | }
38 |
39 | export default reducer
40 |
--------------------------------------------------------------------------------
/demo/redux/store.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux'
2 | import reducer from './reducer'
3 |
4 | export const store = createStore(reducer)
5 |
--------------------------------------------------------------------------------
/demo/redux/worker.ts:
--------------------------------------------------------------------------------
1 | import { expose, createProxyStore } from '../../src' // replace with react-redux-worker
2 | import { store } from './store'
3 |
4 | const proxyStore = createProxyStore(store)
5 | expose(proxyStore, self)
6 |
--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": false,
4 | "baseUrl": ".",
5 | "esModuleInterop": true,
6 | "jsx": "react",
7 | "lib": ["es2015", "es2016", "dom"],
8 | "module": "commonjs",
9 | "moduleResolution": "node",
10 | "noImplicitAny": false,
11 | "noUnusedLocals": false,
12 | "noUnusedParameters": false,
13 | "preserveConstEnums": true,
14 | "removeComments": true,
15 | "sourceMap": true,
16 | "strictNullChecks": true,
17 | "target": "es5",
18 | "types": ["node"]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/img/react-redux-worker.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/img/worker-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HerbCaudill/react-redux-worker/69c40b983058ecdab68589aba2606a1a7cb03522/img/worker-demo.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-worker",
3 | "repository": {
4 | "type": "git",
5 | "url": "https://github.com/HerbCaudill/react-redux-worker"
6 | },
7 | "description": "Run a Redux store in a Web Worker",
8 | "keywords": [
9 | "worker",
10 | "web-worker",
11 | "redux",
12 | "react"
13 | ],
14 | "version": "0.1.9",
15 | "license": "MIT",
16 | "main": "dist/index.js",
17 | "module": "dist/react-redux-worker.esm.js",
18 | "typings": "dist/index.d.ts",
19 | "files": [
20 | "dist"
21 | ],
22 | "scripts": {
23 | "dev": "tsdx watch",
24 | "build": "tsdx build",
25 | "test": "tsdx test --env=jsdom",
26 | "lint": "tsdx lint",
27 | "prepublish": "tsdx build",
28 | "start": "cd ./demo && yarn start"
29 | },
30 | "dependencies": {
31 | "@types/lodash.isequal": "^4.5.5",
32 | "comlinkjs": "3",
33 | "lodash.isequal": "4"
34 | },
35 | "devDependencies": {
36 | "@types/jest": "24",
37 | "@types/react": "16",
38 | "@types/react-dom": "16",
39 | "husky": "3",
40 | "react": "16",
41 | "react-dom": "16",
42 | "redux": "4",
43 | "tsdx": "0",
44 | "tslib": "1",
45 | "typescript": "3"
46 | },
47 | "peerDependencies": {
48 | "react": ">=16",
49 | "redux": ">=4"
50 | },
51 | "prettier": {
52 | "printWidth": 80,
53 | "semi": false,
54 | "singleQuote": true,
55 | "trailingComma": "es5"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/ProxyStore.ts:
--------------------------------------------------------------------------------
1 | import { AnyAction } from 'redux'
2 |
3 | export type ProxyStore = {
4 | getState(): Promise
5 | dispatch(action: A): Promise
6 | subscribe(
7 | listener: (state: State) => void,
8 | selector?: (root: State) => Promise | State
9 | ): Promise
10 | unsubscribe(listenerId: number): Promise
11 | }
12 |
--------------------------------------------------------------------------------
/src/StateContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | export const StateContext = React.createContext(null as any)
3 |
--------------------------------------------------------------------------------
/src/StoreContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | export const StoreContext = React.createContext(null as any)
3 |
--------------------------------------------------------------------------------
/src/createProxyStore.ts:
--------------------------------------------------------------------------------
1 | import isEqual from 'lodash.isequal'
2 | import { AnyAction, Store } from 'redux'
3 | import { ProxyStore } from './ProxyStore'
4 | import { uniqueId } from './uniqueId'
5 |
6 | /**
7 | * Since our store is running in a worker process, we provide our application with a proxy
8 | * store that looks to it just like a garden-variety Redux store, except that everything is
9 | * asynchronous, since all communication with the worker is based on handling message events.
10 | *
11 | * Once created, the proxy needs to be exposed using the `expose` function:
12 | * ```js
13 | * import {expose, createProxyStore} from '...'
14 | * const store = createStore(reducer)
15 | * const proxyStore = createProxyStore(store)
16 | * expose(proxyStore, self)
17 | *```
18 | * @param store A regular Redux store created using `Redux.createStore`.
19 | */
20 | export const createProxyStore = (
21 | store: Store
22 | ): ProxyStore => {
23 | const listenerMap = new Map()
24 | return {
25 | async subscribe(onChangeHandler: Function): Promise {
26 | const subscriptionId = uniqueId()
27 | let lastSnapshot = store.getState()
28 | const unsubscribe = store.subscribe(async () => {
29 | const newSnapshot = store.getState()
30 | if (!isEqual(lastSnapshot, newSnapshot)) {
31 | onChangeHandler(newSnapshot)
32 | lastSnapshot = newSnapshot
33 | }
34 | })
35 | listenerMap.set(subscriptionId, unsubscribe)
36 | return subscriptionId
37 | },
38 | async unsubscribe(subscriptionId: number) {
39 | const listener = listenerMap.get(subscriptionId)
40 | if (listener) listener()
41 | listenerMap.delete(subscriptionId)
42 | },
43 | async getState() {
44 | return store.getState()
45 | },
46 | async dispatch(action: AnyAction) {
47 | store.dispatch(action)
48 | },
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/expose.ts:
--------------------------------------------------------------------------------
1 | import { ProxyStore } from './ProxyStore'
2 | import * as Comlink from 'comlinkjs'
3 |
4 | /**
5 | * Uses `Comlink` to expose a `proxyStore` as a worker.
6 | *
7 | * Example:
8 | * ```js
9 | * import {expose, createProxyStore} from '...'
10 | * const store = createStore(reducer)
11 | * const proxyStore = createProxyStore(store)
12 | * expose(proxyStore, self)
13 | *```
14 | * @param proxyStore A proxy store created using `createProxyStore`
15 | * @param context Typically `self` on a worker module
16 | */
17 | export const expose = (
18 | proxyStore: ProxyStore,
19 | context: Comlink.Endpoint | Window
20 | ): void => {
21 | Comlink.expose({ ...proxyStore }, context)
22 | }
23 |
--------------------------------------------------------------------------------
/src/getProvider.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { useStore } from './hooks'
3 | import { StateContext } from './StateContext'
4 | import { StoreContext } from './StoreContext'
5 |
6 | interface ProviderProps {
7 | children: React.ReactNode
8 | fallback?: JSX.Element
9 | }
10 |
11 | const empty = <>>
12 |
13 | export function getProvider(worker: Worker) {
14 | return function Provider({ children, fallback = empty }: ProviderProps) {
15 | const [state, store] = useStore(worker)
16 |
17 | const provider = (
18 |
19 | {children}
20 |
21 | )
22 |
23 | return state ? provider : fallback
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/hooks.ts:
--------------------------------------------------------------------------------
1 | import * as Comlink from 'comlinkjs'
2 | import { useContext, useEffect, useState } from 'react'
3 | import { AnyAction, Dispatch } from 'redux'
4 | import { ProxyStore } from './ProxyStore'
5 | import { StoreContext } from './StoreContext'
6 | import { StateContext } from './StateContext'
7 | type Selector = (state: T) => any
8 |
9 | export function useStore(worker: Worker): [T | null, ProxyStore] {
10 | const [state, setState] = useState(null)
11 | const proxyStore: ProxyStore = Comlink.proxy(worker) as any
12 |
13 | // get current state then subscribe to it
14 | useEffect(() => {
15 | proxyStore.getState().then(async (s: T) => setState(s))
16 | proxyStore.subscribe(Comlink.proxyValue((s: T) => setState(s)))
17 | }, []) // only on first render
18 |
19 | return [state, proxyStore]
20 | }
21 |
22 | export const useSelector = (selector: Selector): any => {
23 | const state = useContext(StateContext)
24 | return selector(state)
25 | }
26 |
27 | export const useDispatch = (): Dispatch =>
28 | useContext(StoreContext).dispatch
29 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { useDispatch, useStore, useSelector } from './hooks'
2 | export { getProvider } from './getProvider'
3 | export { createProxyStore } from './createProxyStore'
4 | export { expose } from './expose'
5 |
--------------------------------------------------------------------------------
/src/uniqueId.ts:
--------------------------------------------------------------------------------
1 | let lastId = 0
2 | export const uniqueId = () => ++lastId
3 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "alwaysStrict": true,
4 | "baseUrl": "./",
5 | "declaration": true,
6 | "esModuleInterop": true,
7 | "importHelpers": true,
8 | "jsx": "react",
9 | "lib": ["dom", "esnext"],
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "noFallthroughCasesInSwitch": true,
13 | "noImplicitAny": true,
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "paths": {
19 | "*": ["src/*", "node_modules/*"]
20 | },
21 | "rootDir": "./",
22 | "sourceMap": true,
23 | "strict": true,
24 | "strictFunctionTypes": true,
25 | "strictNullChecks": true,
26 | "strictPropertyInitialization": true,
27 | "target": "es5"
28 | },
29 | "include": ["src", "types"]
30 | }
31 |
--------------------------------------------------------------------------------