global-store v1.0.0-beta.21
global-store
global-store
provides version stable stores for library.
Who need this
- library that can be used by other libraries, and
- library that has state, and/or
- library that want to protect its state from modification.
Why do you need this
If you use a file level variable to store some states, and your library might be used by other libraries, the state it store would scatter around the memory and you will get inconsistent result:
const registry = {}
export function addComponent(name: string, component: any) {
registry[name] = component
}
This is because when your library is used by other libraries, they may use different versions. For example:
app
- some-library@1.5
- your-library@1.0
- your-library@2.0
Since the versions are not compatible,
both versions of your library are loaded thus two instance of the registry
variable exists.
Solution to this problem is to use some form of global storage such as process.env
in NodeJS,
and localStorage
or global variable in browser.
The problem is that these mechanism are shared by everything else in the application, completely exposed to everyone, and there is no mechanism to consolidate your state when they are populated my each copy of your library.
These are the problems addressed by global-store
API
createStore()
createStore()
creates a version stable store.
import { createStore } from 'global-store'
const store = createStore(
'your-module',
'unique-string',
previous => ({ ...previous, prop1: false, prop2: [] as string[] })
)
console.log(store.get().prop1) // false
store.get().prop1 = true
store.get().prop2.push('a')
console.log(store.get()) // { prop1: true, prop2: ['a'] }
createStore(moduleName, key, initializer)
moduleName: string
: Name of your module. This is typically your npm package name.key: string | symbol
: Together withmoduleName
,key
+moduleName
forms an unique id to the store. Thekey
should be unique for each store you create. You can use some random string such as UUID. You can also usesymbol
, but not that you need to use theSymbol.for('key')
variant asSymbol()
does not work for this purpose.initializer: (previous) => initValue
: initializer to initialize the store. Since there may be multiple copies of your library loaded, multiple calls tocreateStore()
may occurs. For the first call, theprevious
argument will be an empty object. For subsequence calls, it will be the value returned by the previous call. Since there is no way to control the load order,createStore()
can be called by a newer version of your libary before an older version of your libary calls it. To property setup your store, you can use a property such asrevisions
orversions
to help this process.
Store#get()
Gets the store value. Also use this to update the store.
import { createStore } from 'global-store'
const store = createStore(
'your-module',
'general:300c47d7-b3a8-43ee-9dea-1e05a7b34240',
p => ({ ...p, a: 1 })
)
store.get().a = 2
console.log(store.get().a) // 2
Store#reset()
Reset the store to its initial value.
This is used mostly in your test, so that the tests would not interferred each other.
createReadonlyStore()
createReadonlyStore()
creates a version stable store that prevents modification.
It signature is the same as createStore()
.
The returned ReadonlyStore
has the following additional features:
ReadonlyStore#get()
When the store is created,
calling get()
would result in error if lock()
is not called.
This avoids the store to be used accidentially without protection.
Readonly#lock()
Lock the store, making it read only.
Then the store is locked, the following happens:
- the value is frozen, making each property read only.
- if the property is an array, it is also frozen, making it unable to add or remove entry.
get()
is open to be used.reset()
results in error.getWritable()
results in error.openForTesting()
results in error.
lock()
takes an optional finalizer
argument.
It can contains properties matching the property names of the store,
where each one is a transform function for that property.
It allows you to process the store before it is locked.
The typical use cases are to validate, clean up, transform, and freeze the values.
import { createReadonlyStore } from 'global-store'
const store = createStore(
'your-module',
'general:ea305f50-c48c-4d18-97ef-4c8e8f130446',
p => ({ ...p, a: 1, b: [], c: {} })
)
store.lock({
b: values => values.map(v => Object.freeze(v)),
c: value => Object.freeze(value),
prev: value => undefined // make the older version property disappear.
})
ReadonlyStore#getWritable()
Before the store is locked,
you need a mechanism to access the store and configure it.
getWritable()
by pass the check and allow you to do that.
Once the store is locked, calling getWritable()
results in error.
ReadonlyStore#openForTesting()
During testing,
you need a mechanism to allow the get()
calls to go through without locking the store.
openForTesting()
tells the store to turn off all checks so it can be used during test.
Due to its power, you should not have any code calling this method except in your test code.
Installation
npm install global-store
yarn add global-store
Bundling
If your library will be a standalone bundle, make sure to exclude global-store
.
If not, there will be multiple copies of global-store
loaded and will completely defeat the purose.
You also should mark global-store
as a peer dependency and tell people who use your library to include global-store
as their dependency.
Any application that eventually uses your library should do the same, install global-store
as their own dependency.
12 months ago
2 years ago
2 years ago
2 years ago
3 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago