browser-config
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -5,10 +5,11 @@ "use strict"; | ||
const nullStorage = { | ||
length: 0, | ||
setItem() { | ||
console.warn('Storage not supported'); | ||
return; | ||
}, | ||
getItem() { | ||
console.warn('Storage not supported'); | ||
return null; | ||
}, | ||
removeItem() { | ||
console.warn('Storage not supported'); | ||
return; | ||
} | ||
@@ -40,7 +41,4 @@ }; | ||
*keys() { | ||
for (const i in this.engine) { | ||
if (!Object.prototype.hasOwnProperty.call(this.engine, i)) { | ||
continue; | ||
} | ||
yield i; | ||
for (let i = 0; i < this.engine.length; i++) { | ||
yield this.engine.key(i); | ||
} | ||
@@ -47,0 +45,0 @@ } |
@@ -17,5 +17,8 @@ import { IDriver } from "./driver"; | ||
static keys(instance: BaseStorage): Generator<string, void, unknown>; | ||
static driver(instance: BaseStorage): IDriver; | ||
static clear(instance: BaseStorage): number; | ||
static savePending(instance: BaseStorage): void; | ||
static clearCache(instance: BaseStorage): BaseStorage; | ||
static values(instance: BaseStorage): any; | ||
static update(instance: BaseStorage, data: Record<string, any>): BaseStorage; | ||
static update(instance: BaseStorage, data: Record<string, any> | string, value?: any): BaseStorage; | ||
static set(instance: BaseStorage, data: Record<string, any>): void; | ||
@@ -25,3 +28,3 @@ static id(instance: BaseStorage): string; | ||
private readonly [OPTION]; | ||
private readonly [DATA]; | ||
private [DATA]; | ||
private readonly [DIRTY]; | ||
@@ -33,4 +36,5 @@ private [PENDING]; | ||
[REMOVE](key: Key): this; | ||
toJSON(): any; | ||
[Symbol.iterator](): Generator<any[], void, unknown>; | ||
} | ||
export {}; |
@@ -13,2 +13,5 @@ "use strict"; | ||
const PENDING = Symbol('pending'); | ||
function isOwn(obj, prop) { | ||
return Object.prototype.hasOwnProperty.call(obj, prop); | ||
} | ||
class BaseStorage { | ||
@@ -72,2 +75,5 @@ constructor(id, option = {}) { | ||
} | ||
static driver(instance) { | ||
return instance[OPTION].driver; | ||
} | ||
static clear(instance) { | ||
@@ -81,2 +87,21 @@ let count = 0; | ||
} | ||
static savePending(instance) { | ||
for (const dirty of instance[DIRTY].update) { | ||
const key = `${instance[ID]}[${dirty}]`, value = instance[DATA][dirty]; | ||
if (typeof value === "undefined") { | ||
instance[OPTION].driver.remove(key); | ||
continue; | ||
} | ||
instance[OPTION].driver.set(key, JSON.stringify(value)); | ||
} | ||
instance[PENDING].update = false; | ||
instance[DIRTY].update.clear(); | ||
} | ||
static clearCache(instance) { | ||
this.savePending(instance); | ||
for (const i in instance[DATA]) { | ||
delete instance[DATA][i]; | ||
} | ||
return instance; | ||
} | ||
static values(instance) { | ||
@@ -89,5 +114,8 @@ const values = {}; | ||
} | ||
static update(instance, data) { | ||
static update(instance, data, value) { | ||
if (typeof data === "string") { | ||
return this.update(instance, { [data]: value }); | ||
} | ||
for (const o in data) { | ||
if (!Object.prototype.hasOwnProperty.call(data, o)) { | ||
if (!isOwn(data, o)) { | ||
continue; | ||
@@ -114,7 +142,3 @@ } | ||
setTimeout(() => { | ||
for (const dirty of this[DIRTY].update) { | ||
this[OPTION].driver.set(`${this[ID]}[${dirty}]`, JSON.stringify(this[DATA][dirty])); | ||
} | ||
this[PENDING].update = false; | ||
this[DIRTY].update.clear(); | ||
BaseStorage.savePending(this); | ||
}, 0); | ||
@@ -131,5 +155,10 @@ return this; | ||
} | ||
const data = JSON.parse(val); | ||
this[DATA][key] = data; | ||
return data; | ||
try { | ||
const data = JSON.parse(val); | ||
this[DATA][key] = data; | ||
return data; | ||
} | ||
catch (e) { | ||
return; | ||
} | ||
} | ||
@@ -155,2 +184,5 @@ [REMOVE](key) { | ||
} | ||
toJSON() { | ||
return BaseStorage.values(this); | ||
} | ||
*[Symbol.iterator]() { | ||
@@ -163,8 +195,8 @@ for (const key of BaseStorage.keys(this)) { | ||
exports.default = BaseStorage; | ||
function getId(str, n = 8) { | ||
function getId(name, n = 8) { | ||
let code = ''; | ||
for (let i = 0; i < str.length; i++) { | ||
code += str.charCodeAt(i); | ||
for (let i = 0; i < name.length; i++) { | ||
code += name.charCodeAt(i); | ||
} | ||
return Number(code).toString(16).substr(0, n); | ||
} |
{ | ||
"name": "browser-config", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"main": "./lib/index.js", | ||
@@ -24,3 +24,4 @@ "license": "MIT", | ||
"build": "tsc --project tsconfig.build.json", | ||
"test": "jest --env=jsdom" | ||
"test": "jest --env=jsdom", | ||
"test:watch": "yarn test --watch ./test" | ||
}, | ||
@@ -35,2 +36,2 @@ "devDependencies": { | ||
"dependencies": {} | ||
} | ||
} |
142
README.md
@@ -5,5 +5,7 @@ # Browser config | ||
> It cache the data locally to reduce number of queries to the actual storage and serialize / de-serialize only if needed. | ||
## Examples | ||
## Installation | ||
### Installation | ||
@@ -18,40 +20,85 @@ yarn add browser-config | ||
```js | ||
const config = new Store(); | ||
```ts | ||
const store = new Store(); | ||
``` | ||
Saving person to localStorage | ||
```ts | ||
store.person = { | ||
firstName: "John", | ||
lastName: "Doe", | ||
age: 22 | ||
}; | ||
``` | ||
> This will save the person in cache only, serialization and store into localStorage will happen in next event cycle. | ||
> So even if we save same property multiple times it will touch the localStorage only once. | ||
// setting value | ||
config.name = "Hello" | ||
// By default it save data to the cache only and update/serialize data in next event loop. | ||
Getting person | ||
```ts | ||
console.log(store.person) | ||
``` | ||
{ | ||
firstName: "John", | ||
lastName: "Doe", | ||
age: 22 | ||
} | ||
> if the person is present in the cache, it will return from cache, otherwise it will query localStorage, deserialize, save into cache and return person. | ||
// getting value | ||
config.name // "hello" | ||
// you can get config.name again and again it won't request to storage or deserialize, | ||
// instead it will get data from the cache only. | ||
Getting all keys | ||
[...Store.keys(config)] // ["name"] | ||
// Store.keys return generators, you need to spread it to use as an array. | ||
```ts | ||
console.log([...Store.keys(store)]); | ||
``` | ||
[ | ||
"person" | ||
] | ||
Object.fromEntries(config) // { name: "Hello" } | ||
// or | ||
Store.values(config) // { name: "Hello" } | ||
> Store.keys() will return generator, need to spread to use as an array | ||
// deleting | ||
delete config.name | ||
config.name // undefined | ||
Getting all values | ||
// clearing all the data | ||
Store.clear(config) // length of cleared items; | ||
```ts | ||
console.log(Object.fromEntries(store)); | ||
``` | ||
{ | ||
person: { | ||
firstName: "John", | ||
lastName: "Doe", | ||
age: 22 | ||
} | ||
} | ||
or | ||
```ts | ||
console.log(Store.values(store)) | ||
``` | ||
Deleting | ||
```ts | ||
delete store.person | ||
``` | ||
or | ||
```ts | ||
store.person = undefined; | ||
``` | ||
Clear everything | ||
```ts | ||
Store.clear(store); | ||
``` | ||
```js | ||
// can support any serializable data | ||
store.users = [{ name: 'Hello' }] | ||
store.users // [{ name: 'Hello' }] | ||
config.users = [{ name: 'Hello' }] | ||
config.users // [{ name: 'Hello' }] | ||
// mutation is not supported | ||
config.users.push({name: 'New User'}); // x will not work | ||
store.users.push({name: 'New User'}); // x will not work | ||
// adding new value to array | ||
config.users = [ ...config.users, { name: 'New User'}] | ||
store.users = [ ...config.users, { name: 'New User'}] | ||
``` | ||
## Multiple instances | ||
### Multiple instances | ||
@@ -71,3 +118,3 @@ ```js | ||
## Iterate through all the data | ||
### Iterate through all the data | ||
```js | ||
@@ -82,7 +129,7 @@ const config = new Store('default') | ||
## Session storage | ||
### Session storage | ||
By default it will save to localStorage and it is permanent, you can save it sessionStorage as well. | ||
```ts | ||
const config = new Store('some_id', { | ||
const config = new Store(undefined, { | ||
validity: "session" | ||
@@ -94,3 +141,3 @@ }); | ||
## Typescript users | ||
### Typescript users | ||
```ts | ||
@@ -107,3 +154,3 @@ interface IPerson { | ||
## Custom driver | ||
### Custom driver | ||
Sometimes you need to save data to other than localStorage, sessionStorage let's say in cookies. | ||
@@ -153,6 +200,24 @@ | ||
## References | ||
### toJSON | ||
### instantiate | ||
```ts | ||
const store = new Store(); | ||
store.name = "hello" | ||
store.email = "hello@world.com"; | ||
JSON.stringify(store) // {"name": "hello", "email": "hello@world.com"} | ||
store.toJSON = "Something else"; | ||
// toJSON is a built-in method and it is only method/property built-in it doesn't mean you can't store this as a property. | ||
// you can still use but there is a slightly different approach for accessing the value | ||
// if using typescript use can see type error | ||
store.toJSON // [Function toJSON] | ||
store.toJSON().toJSON // 'Something else' | ||
``` | ||
## Reference | ||
### Instantiate | ||
```ts | ||
@@ -169,7 +234,10 @@ import Store from "browser-config"; | ||
### static methods | ||
* `Store.id(store): string` generated or passed id | ||
* `Store.id(store: Store): string` generated or passed id | ||
* `Store.keys(store: Store): Iterable<string>` get all the keys | ||
* `Store.values(store: Store): {[key: string]: any}` get all the values | ||
* `Store.clear(store): string` clearing all the values | ||
* `Store.update(store, data: object)` update values in bulk | ||
* `Store.set(store, data: object)` it will delete all the existing value and set the provided object | ||
* `Store.clear(store: Store): string` clearing all the values | ||
* `Store.update(store: Store, data: object)` update values in bulk | ||
* `Store.set(store: Store, data: object)` it will delete all the existing value and set the provided object | ||
* `Store.clearCache(store: Store)` it will delete cache | ||
* `Store.savePending(store: Store)` force to save now into the storage instead of waiting for next event cycle. | ||
@@ -9,10 +9,11 @@ export interface IDriver { | ||
const nullStorage: Storage = { | ||
length: 0, | ||
setItem() { | ||
console.warn('Storage not supported'); | ||
return; | ||
}, | ||
getItem() { | ||
console.warn('Storage not supported'); | ||
return null; | ||
}, | ||
removeItem() { | ||
console.warn('Storage not supported'); | ||
return; | ||
} | ||
@@ -48,9 +49,6 @@ } as any; | ||
*keys() { | ||
for (const i in this.engine) { | ||
if (!Object.prototype.hasOwnProperty.call(this.engine, i)) { | ||
continue; | ||
} | ||
yield i; | ||
} | ||
for (let i = 0; i < this.engine.length; i++) { | ||
yield this.engine.key(i)!; | ||
} | ||
} | ||
} |
@@ -13,2 +13,7 @@ import { DefaultDriver, IDriver } from "./driver"; | ||
function isOwn(obj: object, prop: any) { | ||
return Object.prototype.hasOwnProperty.call(obj, prop); | ||
} | ||
export interface IOption { | ||
@@ -43,2 +48,6 @@ driver: IDriver; | ||
static driver(instance: BaseStorage) { | ||
return instance[OPTION].driver; | ||
} | ||
static clear(instance: BaseStorage) { | ||
@@ -53,2 +62,23 @@ let count = 0; | ||
static savePending(instance: BaseStorage) { | ||
for (const dirty of instance[DIRTY].update) { | ||
const key = `${instance[ID]}[${dirty}]`, value = instance[DATA][dirty]; | ||
if (typeof value === "undefined") { | ||
instance[OPTION].driver.remove(key); | ||
continue; | ||
} | ||
instance[OPTION].driver.set(key, JSON.stringify(value)); | ||
} | ||
instance[PENDING].update = false | ||
instance[DIRTY].update.clear(); | ||
} | ||
static clearCache(instance: BaseStorage) { | ||
this.savePending(instance); | ||
for (const i in instance[DATA]) { | ||
delete instance[DATA][i]; | ||
} | ||
return instance; | ||
} | ||
static values(instance: BaseStorage) { | ||
@@ -62,5 +92,8 @@ const values: any = {}; | ||
static update(instance: BaseStorage, data: Record<string, any>) { | ||
static update(instance: BaseStorage, data: Record<string, any> | string, value?: any): BaseStorage { | ||
if (typeof data === "string") { | ||
return this.update(instance, {[data]: value}); | ||
} | ||
for (const o in data) { | ||
if (!Object.prototype.hasOwnProperty.call(data, o)) { | ||
if (!isOwn(data, o)) { | ||
continue; | ||
@@ -84,3 +117,3 @@ } | ||
private readonly [OPTION]: IOption; | ||
private readonly [DATA]: Record<string, any> = {}; | ||
private [DATA]: Record<string, any> = {}; | ||
private readonly [DIRTY] = { | ||
@@ -130,7 +163,3 @@ remove: new Set<Key>(), | ||
setTimeout(() => { | ||
for (const dirty of this[DIRTY].update) { | ||
this[OPTION].driver.set(`${this[ID]}[${dirty}]`, JSON.stringify(this[DATA][dirty])); | ||
} | ||
this[PENDING].update = false | ||
this[DIRTY].update.clear(); | ||
BaseStorage.savePending(this); | ||
}, 0) | ||
@@ -148,5 +177,9 @@ return this; | ||
} | ||
const data = JSON.parse(val); | ||
this[DATA][key] = data; | ||
return data; | ||
try { | ||
const data = JSON.parse(val); | ||
this[DATA][key] = data; | ||
return data; | ||
} catch (e) { | ||
return; | ||
} | ||
} | ||
@@ -174,2 +207,6 @@ | ||
toJSON() { | ||
return BaseStorage.values(this); | ||
} | ||
*[Symbol.iterator]() { | ||
@@ -182,8 +219,8 @@ for (const key of BaseStorage.keys(this)) { | ||
function getId(str: string, n = 8) { | ||
function getId(name: string, n = 8) { | ||
let code = ''; | ||
for (let i = 0; i < str.length; i++) { | ||
code += str.charCodeAt(i); | ||
for (let i = 0; i < name.length; i++) { | ||
code += name.charCodeAt(i); | ||
} | ||
return Number(code).toString(16).substr(0, n); | ||
} | ||
} |
@@ -5,7 +5,21 @@ import { DefaultDriver } from "../lib/driver"; | ||
test("setting value", () => { | ||
const settingFn = jest.spyOn(localStorage.__proto__, 'setItem'); | ||
expect(driver.set("name", "adil")).toBe(driver); | ||
expect(settingFn).toHaveBeenCalledWith('name', 'adil'); | ||
}) | ||
test("setting value temp", () => { | ||
const driver = new DefaultDriver(true); | ||
const settingFn = jest.spyOn(sessionStorage.__proto__, 'setItem'); | ||
expect(driver.set("name", "adil")).toBe(driver); | ||
expect(settingFn).toHaveBeenCalledWith('name', 'adil'); | ||
settingFn.mockRestore(); | ||
}); | ||
test("getting value", () => { | ||
expect(driver.get("name")).toBe("adil"); | ||
const gettingFn = jest.spyOn(localStorage.__proto__, 'getItem'); | ||
gettingFn.mockReturnValue('world'); | ||
expect(driver.get('hello')).toBe('world'); | ||
expect(gettingFn).toHaveBeenCalledWith('hello'); | ||
gettingFn.mockRestore(); | ||
}); | ||
@@ -22,2 +36,3 @@ | ||
expect([...driver.keys()]).toEqual(["name", "email"]); | ||
}); | ||
}); | ||
import { BaseStorage, IDriver } from "../"; | ||
class Driver implements IDriver { | ||
public store: Record<string, string> = {}; | ||
set(key: string, val: string) { | ||
this.store[key] = val; | ||
return this; | ||
} | ||
get(key: string) { | ||
return this.store[key]; | ||
} | ||
remove(key: string) { | ||
delete this.store[key]; | ||
return this; | ||
} | ||
keys() { | ||
return Object.keys(this.store); | ||
} | ||
} | ||
class Test extends BaseStorage { | ||
@@ -24,2 +42,24 @@ [key: string]: any; | ||
test('setting', () => { | ||
const test = new Test(); | ||
const settingFn = jest.spyOn(Test.driver(test), 'set'); | ||
test.name = 'apple'; | ||
test.name = 'mango'; | ||
expect(settingFn).not.toHaveBeenCalled(); | ||
jest.advanceTimersToNextTimer(); | ||
expect(settingFn).toHaveBeenNthCalledWith(1, `${Test.id(test)}[name]`, JSON.stringify('mango')); | ||
settingFn.mockRestore(); | ||
}) | ||
test('getting', () => { | ||
const test = new Test(); | ||
const gettingFn = jest.spyOn(Test.driver(test), 'get'); | ||
gettingFn.mockReturnValue('"hello world"') | ||
expect(test.name).toBe('hello world'); | ||
expect(test.name).toBe('hello world'); | ||
expect(test.name).toBe('hello world'); | ||
expect(gettingFn).toHaveBeenNthCalledWith(1, `${Test.id(test)}[name]`); | ||
gettingFn.mockRestore(); | ||
}) | ||
test("setting/getting/removing", () => { | ||
@@ -31,2 +71,3 @@ const storage = new Test(); | ||
expect(storage.name).toBe(undefined); | ||
expect(storage.name2).toBe(undefined); | ||
}); | ||
@@ -53,24 +94,2 @@ | ||
test("cache on setting", async () => { | ||
const data = { name: 'hello' }; | ||
const storage = new Test(); | ||
storage.data = data; | ||
expect(storage.data).toBe(data); | ||
const storage2 = new Test(); | ||
expect(storage2.data).not.toEqual(storage.data); | ||
jest.advanceTimersToNextTimer(); | ||
expect(storage2.data).toEqual(storage.data); | ||
}); | ||
test("cache on getting", async () => { | ||
const data = { name: 'hello' }; | ||
const storage = new Test(); | ||
storage.data = data; | ||
const storage2 = new Test(); | ||
expect(storage2.data).toBe(storage2.data); | ||
expect(storage2.data).not.toEqual(data); | ||
jest.advanceTimersToNextTimer(); | ||
expect(storage2.data).toEqual(data); | ||
}); | ||
test("cache on removing", async () => { | ||
@@ -101,28 +120,11 @@ const storage = new Test('cache remove'); | ||
test("custom driver", async () => { | ||
const store: any = {} | ||
class Driver implements IDriver { | ||
set(key: string, val: string) { | ||
store[key] = val; | ||
return this; | ||
} | ||
get(key: string) { | ||
return store[key]; | ||
} | ||
remove(key: string) { | ||
delete store[key]; | ||
return this; | ||
} | ||
keys() { | ||
return Object.keys(store); | ||
} | ||
} | ||
const storage = new Test("1", { driver: new Driver() }); | ||
const driver = new Driver(); | ||
const storage = new Test("1", { driver }); | ||
storage.data = 'hello'; | ||
jest.advanceTimersToNextTimer(); | ||
expect(store['1[data]']).toBe('"hello"'); | ||
expect(driver.store['1[data]']).toBe('"hello"'); | ||
}); | ||
test("validity", () => { | ||
const test = new Test('validity', { | ||
const test = new Test(undefined, { | ||
validity: "session" | ||
@@ -132,3 +134,39 @@ }); | ||
jest.advanceTimersToNextTimer(); | ||
expect(sessionStorage.getItem(`validity[name]`)).toBe(JSON.stringify('Hello')); | ||
expect(sessionStorage.getItem(`${Test.id(test)}[name]`)).toBe(JSON.stringify('Hello')); | ||
}); | ||
test("setting undefined value", () => { | ||
const test = new Test(); | ||
test.data = 1; | ||
jest.advanceTimersToNextTimer(); | ||
test.data = undefined; | ||
expect(test.data).toBe(undefined); | ||
jest.advanceTimersToNextTimer(); | ||
expect(localStorage.getItem(`${Test.id(test)}[data]`)).toBeNull(); | ||
}) | ||
test("clear cache", () => { | ||
const test = new Test(); | ||
const data = {name: 'No name'}; | ||
test.data = data; | ||
expect(test.data).toBe(data); | ||
Test.clearCache(test); | ||
expect(test.data).not.toBe(data); | ||
jest.advanceTimersToNextTimer(); | ||
expect(test.data).toEqual(data); | ||
}); | ||
test('toJSON', () => { | ||
const test = new Test('tojson'); | ||
test.name = "Adil"; | ||
test.email = "adil.sudo@gmail.com"; | ||
expect(JSON.stringify(test)).toBe(JSON.stringify({ name: "Adil", email: "adil.sudo@gmail.com" })); | ||
}); | ||
test('toJSON as key', () => { | ||
const test = new Test('tojsonkey'); | ||
(test as any).toJSON = "Adil"; | ||
jest.advanceTimersToNextTimer(); | ||
expect(test.toJSON).toBeInstanceOf(Function); | ||
expect(test.toJSON().toJSON).toBe('Adil'); | ||
}); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
31739
790
236
0