Socket
Socket
Sign inDemoInstall

browser-config

Package Overview
Dependencies
0
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.2.0 to 0.3.0

14

lib/driver.js

@@ -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": {}
}
}

@@ -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');
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc