Comparing version 0.4.0 to 0.5.0
@@ -0,7 +1,7 @@ | ||
import type { Keys } from './types'; | ||
export { default as Validations } from './validations'; | ||
/** | ||
* Configuration for StoreObjectConfig | ||
* @template O The stored object | ||
* Configuration options used between both storeObject and storeSeparate | ||
*/ | ||
export interface StoreObjectConfig<O extends Record<string, any>> { | ||
interface CommonConfig { | ||
/** | ||
@@ -13,12 +13,2 @@ * Whether or not to check localStorage when an object key is retrieved | ||
/** | ||
* Whether the stored object only contains/stores *some* of the keys on the serialized object. | ||
* This is useful if you want an object to look at only some keys of a localStorage object | ||
* without overwriting the other ones. | ||
* | ||
* It's important to note that passing this option effectively enables key validation: | ||
* any keys that were not passed are ignored and not passed to validate or modify | ||
* @default false | ||
*/ | ||
partial?: boolean; | ||
/** | ||
* Called whenever a key should be set | ||
@@ -36,4 +26,32 @@ * @param value The value being set | ||
/** | ||
* Function to parse object. Defaults to `JSON.parse`. | ||
* Any validation should **NOT** be done here, but in the validate method | ||
* @default JSON.parse | ||
*/ | ||
parse?: (value: string) => any; | ||
/** | ||
* Function to stringify object. Defaults to `JSON.stringify`. | ||
* Any validation should **NOT** be done here, but in the validate method | ||
* @default JSON.stringify | ||
*/ | ||
stringify?: (value: any) => string; | ||
} | ||
/** | ||
* Configuration for StoreObjectConfig | ||
* @template O The stored object | ||
*/ | ||
export interface StoreObjectConfig<O extends Record<string, any>> extends CommonConfig { | ||
/** | ||
* Whether the stored object only contains/stores *some* of the keys on the serialized object. | ||
* This is useful if you want an object to look at only some keys of a localStorage object | ||
* without overwriting the other ones. | ||
* | ||
* It's important to note that passing this option effectively enables key validation: | ||
* any keys that were not passed are ignored and not passed to validate or modify | ||
* @default false | ||
*/ | ||
partial?: boolean; | ||
/** | ||
* Validate an object before setting it in localStorage or reading it. | ||
* Can confirm/deny if the object is valid, along with an optional error message if it is not | ||
* Can confirm/deny if the object is valid, along with an optional error message if it is invalid | ||
* | ||
@@ -50,14 +68,2 @@ * @returns A boolean to confirm validity or false and optionally an Error instance to deny validity | ||
modify?(value: O, action: 'get' | 'set'): O; | ||
/** | ||
* Function to parse object. Defaults to `JSON.parse`. | ||
* Any validation should **NOT** be done here, but in the validate method | ||
* @default JSON.parse | ||
*/ | ||
parse?: (value: string) => any; | ||
/** | ||
* Function to stringify object. Defaults to `JSON.stringify`. | ||
* Any validation should **NOT** be done here, but in the validate method | ||
* @default JSON.stringify | ||
*/ | ||
stringify?: (value: any) => string; | ||
} | ||
@@ -125,3 +131,3 @@ /** | ||
* | ||
* const myPerson = storeObject( | ||
* const myPerson = storeObject<Person>( | ||
* 'myPerson', | ||
@@ -132,3 +138,3 @@ * { | ||
* minor: true, | ||
* } as Person, | ||
* }, | ||
* { | ||
@@ -139,3 +145,3 @@ * // If the person's age is 18 or greater, set minor to false. | ||
* // and retrieved from it | ||
* validate(value) { | ||
* modify(value) { | ||
* if (value.age >= 18) value.minor = false | ||
@@ -155,4 +161,6 @@ * else value.minor = true | ||
export declare function storeObject<O extends Record<string, any> = Record<string, any>>(lsKey: string, defaults: O, configuration?: StoreObjectConfig<O>): O; | ||
/** Configuration for storeSeparate */ | ||
export interface StoreSeparateConfig { | ||
/** | ||
* Configuration for storeSeparate | ||
*/ | ||
export interface StoreSeparateConfig<O extends Record<string, any>> extends CommonConfig { | ||
/** | ||
@@ -168,6 +176,26 @@ * An optional unique identifier. Prefixes all keys in localStorage | ||
checkGets?: boolean; | ||
/** | ||
* Validate an object before setting it in localStorage or reading it. | ||
* Can confirm/deny if the object is valid, along with an optional error message if it is invalid | ||
* | ||
* @param value A partial version of the originally passed object, | ||
* **containing only the key being get/set** | ||
* @param key The key being get/set | ||
* @returns A boolean to confirm validity or false and optionally an Error instance to deny validity | ||
*/ | ||
validate?(value: Partial<O>, action: 'get' | 'set', key: Keys<O>): boolean | readonly [boolean] | readonly [false, Error]; | ||
/** | ||
* Modify an object before setting it in localStorage or reading it. | ||
* Called after validate. Any valiation should be done in validate and not here | ||
* | ||
* @param value A partial version of the originally passed object, | ||
* **containing only the key being get/set** | ||
* @param key The key being get/set | ||
* @returns A potentially modified version of the object originally passed. | ||
* **Only the key used in the value param will be changed in localStorage** | ||
*/ | ||
modify?(value: Partial<O>, action: 'get' | 'set', key: Keys<O>): Partial<O>; | ||
} | ||
/** | ||
* Set multiple individual keys in localStorage with one object. | ||
* Note that all values must be strings for this method | ||
* Set multiple individual keys in localStorage with one object | ||
* | ||
@@ -180,2 +208,3 @@ * @param defaults The defaults values if they are undefined | ||
* ```typescript | ||
* // No validation | ||
* import { storeSeparate } from 'ls-proxy' | ||
@@ -185,2 +214,4 @@ * | ||
* foo: 'bar', | ||
* abc: 123, | ||
* numbers: [1, 2, 3], | ||
* }) | ||
@@ -190,6 +221,64 @@ * | ||
* console.log(myObj.foo) // Checks localStorage if checkGets is true | ||
* console.log(myObj.abc === localStorage.abc) // true | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* // Validating that the key being set/get is correct | ||
* import { storeSeparate } from 'ls-proxy' | ||
* | ||
* const myObj = storeSeparate( | ||
* { | ||
* foo: 'bar', | ||
* abc: 123, | ||
* }, | ||
* { | ||
* validate(value, action, key) { | ||
* if (key !== 'foo' && key !== 'abc') return false | ||
* return true | ||
* }, | ||
* }, | ||
* ) | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* // Using IDs to avoid conflicting names | ||
* import { storeSeparate } from 'ls-proxy' | ||
* | ||
* const obj1 = storeSeparate({ foo: 'bar' }, { id: 'obj1' }) | ||
* const obj2 = storeSeparate({ foo: 123 }, { id: 'obj2' }) | ||
* | ||
* console.log(obj1.foo) // bar | ||
* console.log(obj2.foo) // 123 | ||
* console.log(localStorage['obj1.foo']) // "bar" | ||
* console.log(localStorage['obj2.foo']) // 123 | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* // Automatically change a key while being set/get | ||
* import { storeSeparate } from 'ls-proxy' | ||
* | ||
* const myObj = storeSeparate( | ||
* { base64Value: 'foo' }, | ||
* { | ||
* modify(value, action, key) { | ||
* if (key === 'base64Value') { | ||
* // Decode base64 on get | ||
* if (action === 'get') value[key] = window.btoa(value[key]!) | ||
* // Encode base64 on set | ||
* else value[key] = window.atob(value[key]!) | ||
* } | ||
* return value | ||
* }, | ||
* }, | ||
* ) | ||
* | ||
* myObj.base64Value = 'bar' // Encoded in localStorage | ||
* console.log(myObj.base64Value) // Logs 'bar', decoded from localStorage | ||
* ``` | ||
*/ | ||
export declare function storeSeparate<O extends Record<string, string> = Record<string, string>>(defaults: O, configuration?: StoreSeparateConfig): O; | ||
export declare function storeSeparate<O extends Record<string, any> = Record<string, any>>(defaults: O, configuration?: StoreSeparateConfig<O>): O; | ||
export as namespace LSProxy; |
205
lib/index.js
@@ -6,18 +6,18 @@ "use strict"; | ||
Object.defineProperty(exports, "Validations", { enumerable: true, get: function () { return validations_1.default; } }); | ||
const setObj = (target, newObj) => Object.entries(newObj).forEach(([k, v]) => (target[k] = v)); | ||
/** | ||
* Fill in default values for CommonConfig | ||
*/ | ||
const commonDefaults = ({ checkGets, set, get, parse, stringify, }) => ({ | ||
checkGets: checkGets !== null && checkGets !== void 0 ? checkGets : true, | ||
set: set !== null && set !== void 0 ? set : ((key, value) => (localStorage[key] = value)), | ||
get: get !== null && get !== void 0 ? get : (value => { var _a; return (_a = localStorage[value]) !== null && _a !== void 0 ? _a : null; }), | ||
parse: parse !== null && parse !== void 0 ? parse : JSON.parse, | ||
stringify: stringify !== null && stringify !== void 0 ? stringify : JSON.stringify, | ||
}); | ||
/** | ||
* Fill in default values for StoreObjectConfig | ||
* @template O The stored object | ||
*/ | ||
const defaultStoreObjectConfig = ({ checkGets, partial, set, get, validate, modify, parse, stringify, }) => { | ||
return { | ||
checkGets: checkGets !== null && checkGets !== void 0 ? checkGets : true, | ||
partial: partial !== null && partial !== void 0 ? partial : false, | ||
set: set !== null && set !== void 0 ? set : ((key, value) => (localStorage[key] = value)), | ||
get: get !== null && get !== void 0 ? get : (value => { var _a; return (_a = localStorage[value]) !== null && _a !== void 0 ? _a : null; }), | ||
validate: validate !== null && validate !== void 0 ? validate : (() => true), | ||
modify: modify !== null && modify !== void 0 ? modify : (value => value), | ||
parse: parse !== null && parse !== void 0 ? parse : JSON.parse, | ||
stringify: stringify !== null && stringify !== void 0 ? stringify : JSON.stringify, | ||
}; | ||
}; | ||
const defaultStoreObjectConfig = ({ checkGets, partial, set, get, validate, modify, parse, stringify, }) => (Object.assign({ partial: partial !== null && partial !== void 0 ? partial : false, validate: validate !== null && validate !== void 0 ? validate : (() => true), modify: modify !== null && modify !== void 0 ? modify : (value => value) }, commonDefaults({ checkGets, set, get, parse, stringify }))); | ||
const shouldObjectProxy = (object) => | ||
@@ -52,3 +52,42 @@ // Check that the target isn't falsey (primarily in case it's null, since typeof null === 'object') | ||
}; | ||
const validateResult = (valid, defaultError) => { | ||
// Throw error on failure | ||
if (typeof valid === 'boolean') { | ||
// Return is bool | ||
if (!valid) | ||
return [valid, defaultError]; | ||
return [valid]; | ||
} | ||
else { | ||
// Return is array | ||
if (!valid[0]) { | ||
if (valid.length === 2) | ||
return [valid[0], valid[1]]; | ||
else | ||
return [valid[0], defaultError]; | ||
} | ||
return [valid[0]]; | ||
} | ||
}; | ||
/** | ||
* Validate and modify an object | ||
* | ||
* @param validate Function to validate the object | ||
* @param modify Function to modify the object | ||
* @param object The object to modify | ||
* @param action Whether the object is being get or set | ||
* @param lsKey The key in localStorage | ||
* @template O The stored object | ||
* @returns The object if valid | ||
*/ | ||
const validOrThrow = (validate, modify, object, action, lsKey) => { | ||
const error = new TypeError(action === 'get' | ||
? `Validation failed while parsing ${lsKey} from localStorage` | ||
: `Validation failed while setting to ${lsKey} in localStorage`); | ||
const valid = validateResult(validate(object, action), error); | ||
if (!valid[0]) | ||
throw valid[1]; | ||
return modify(object, action); | ||
}; | ||
/** | ||
* Store a stringified JSON object in localStorage. | ||
@@ -114,3 +153,3 @@ * This method can use any type that can be serialized. | ||
* | ||
* const myPerson = storeObject( | ||
* const myPerson = storeObject<Person>( | ||
* 'myPerson', | ||
@@ -121,3 +160,3 @@ * { | ||
* minor: true, | ||
* } as Person, | ||
* }, | ||
* { | ||
@@ -128,3 +167,3 @@ * // If the person's age is 18 or greater, set minor to false. | ||
* // and retrieved from it | ||
* validate(value) { | ||
* modify(value) { | ||
* if (value.age >= 18) value.minor = false | ||
@@ -215,44 +254,25 @@ * else value.minor = true | ||
exports.storeObject = storeObject; | ||
const defaultStoreSeparateConfig = ({ id, checkGets, set, get, validate, modify, parse, stringify, }) => (Object.assign({ id, validate: validate !== null && validate !== void 0 ? validate : (() => true), modify: modify !== null && modify !== void 0 ? modify : (value => value) }, commonDefaults({ checkGets, set, get, parse, stringify }))); | ||
/** | ||
* Validate and modify an object | ||
* Validate and modify a value | ||
* | ||
* @param validate Return from the validate function | ||
* @param validate Function to validate the object | ||
* @param modify Function to modify the object | ||
* @param object The object to modify | ||
* @param action Whether the object is being get or set | ||
* @param lsKey The key in localStorage | ||
* @param key The key being validated/modified | ||
* @template O The stored object | ||
* @returns The object if valid | ||
* @returns The value, if valid | ||
*/ | ||
const validOrThrow = (validate, modify, object, action, lsKey) => { | ||
const validOrThrowSeparate = (validate, modify, object, action, key) => { | ||
const error = new TypeError(action === 'get' | ||
? `Validation failed while parsing ${lsKey} from localStorage` | ||
: `Validation failed while setting to ${lsKey} in localStorage`); | ||
const valid = validate(object, action); | ||
// Throw error on failure | ||
if (typeof valid === 'boolean') { | ||
// Return is bool | ||
if (!valid) | ||
throw error; | ||
} | ||
else if (Array.isArray(valid)) { | ||
// Return is array | ||
if (!valid[0]) { | ||
if (valid.length === 2) | ||
throw valid[1]; | ||
else | ||
throw error; | ||
} | ||
} | ||
return modify(object, action); | ||
? `Validation failed while parsing ${key} from localStorage` | ||
: `Validation failed while setting to ${key} in localStorage`); | ||
const valid = validateResult(validate(object, action, key), error); | ||
if (!valid[0]) | ||
throw valid[1]; | ||
return modify(object, action, key); | ||
}; | ||
const defaultStoreSeparateConfig = ({ id, checkGets, }) => { | ||
return { | ||
id, | ||
checkGets: checkGets !== null && checkGets !== void 0 ? checkGets : true, | ||
}; | ||
}; | ||
/** | ||
* Set multiple individual keys in localStorage with one object. | ||
* Note that all values must be strings for this method | ||
* Set multiple individual keys in localStorage with one object | ||
* | ||
@@ -265,2 +285,3 @@ * @param defaults The defaults values if they are undefined | ||
* ```typescript | ||
* // No validation | ||
* import { storeSeparate } from 'ls-proxy' | ||
@@ -270,2 +291,4 @@ * | ||
* foo: 'bar', | ||
* abc: 123, | ||
* numbers: [1, 2, 3], | ||
* }) | ||
@@ -275,24 +298,94 @@ * | ||
* console.log(myObj.foo) // Checks localStorage if checkGets is true | ||
* console.log(myObj.abc === localStorage.abc) // true | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* // Validating that the key being set/get is correct | ||
* import { storeSeparate } from 'ls-proxy' | ||
* | ||
* const myObj = storeSeparate( | ||
* { | ||
* foo: 'bar', | ||
* abc: 123, | ||
* }, | ||
* { | ||
* validate(value, action, key) { | ||
* if (key !== 'foo' && key !== 'abc') return false | ||
* return true | ||
* }, | ||
* }, | ||
* ) | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* // Using IDs to avoid conflicting names | ||
* import { storeSeparate } from 'ls-proxy' | ||
* | ||
* const obj1 = storeSeparate({ foo: 'bar' }, { id: 'obj1' }) | ||
* const obj2 = storeSeparate({ foo: 123 }, { id: 'obj2' }) | ||
* | ||
* console.log(obj1.foo) // bar | ||
* console.log(obj2.foo) // 123 | ||
* console.log(localStorage['obj1.foo']) // "bar" | ||
* console.log(localStorage['obj2.foo']) // 123 | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* // Automatically change a key while being set/get | ||
* import { storeSeparate } from 'ls-proxy' | ||
* | ||
* const myObj = storeSeparate( | ||
* { base64Value: 'foo' }, | ||
* { | ||
* modify(value, action, key) { | ||
* if (key === 'base64Value') { | ||
* // Decode base64 on get | ||
* if (action === 'get') value[key] = window.btoa(value[key]!) | ||
* // Encode base64 on set | ||
* else value[key] = window.atob(value[key]!) | ||
* } | ||
* return value | ||
* }, | ||
* }, | ||
* ) | ||
* | ||
* myObj.base64Value = 'bar' // Encoded in localStorage | ||
* console.log(myObj.base64Value) // Logs 'bar', decoded from localStorage | ||
* ``` | ||
*/ | ||
function storeSeparate(defaults, configuration = {}) { | ||
const { id, checkGets } = defaultStoreSeparateConfig(configuration); | ||
const { id, checkGets, set, get, validate, modify, parse, stringify } = defaultStoreSeparateConfig(configuration); | ||
const object = Object.assign({}, defaults); | ||
/** Call validOrThrow with relevant parameters by default */ | ||
const vot = (key, value, action) => validOrThrowSeparate(validate, modify, { [key]: value }, action, key)[key]; | ||
// Set defaults | ||
for (const [key, value] of Object.entries(defaults)) { | ||
const keyPrefix = addId(key, id); | ||
if (!localStorage[keyPrefix]) | ||
localStorage[keyPrefix] = value; | ||
const lsValue = get(keyPrefix); | ||
if (!lsValue) { | ||
set(keyPrefix, stringify(vot(key, value, 'set'))); | ||
} | ||
else | ||
object[key] = localStorage[keyPrefix]; | ||
object[key] = vot(parse(lsValue), key, 'get'); | ||
} | ||
return new Proxy(object, { | ||
set(target, key, value) { | ||
localStorage[addId(key, id)] = value; | ||
return Reflect.set(target, key, value); | ||
// Modify object | ||
const modified = vot(key, value, 'set'); | ||
set(addId(key, id), stringify(modified)); | ||
return Reflect.set(target, key, modified); | ||
}, | ||
get(target, key) { | ||
var _a; | ||
if (checkGets) | ||
target[key] = (_a = localStorage[addId(key, id)]) !== null && _a !== void 0 ? _a : defaults[key]; | ||
if (checkGets) { | ||
const valueUnparsed = get(addId(key, id)); | ||
const value = valueUnparsed ? parse(valueUnparsed) : defaults[key]; | ||
target[key] = vot(key, value, 'get'); | ||
} | ||
if (shouldObjectProxy(target[key])) { | ||
// Return a Proxy to the object to catch sets | ||
return nestedProxyHandler(target, key, target[key], this.set); | ||
} | ||
return Reflect.get(target, key); | ||
@@ -299,0 +392,0 @@ }, |
{ | ||
"$schema": "https://json.schemastore.org/package", | ||
"name": "ls-proxy", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "Wrapper around localStorage to easily store JSON objects", | ||
@@ -6,0 +6,0 @@ "repository": "https://gitlab.com/MysteryBlokHed/ls-proxy", |
@@ -21,4 +21,4 @@ # ls-proxy [![Build Badge]](https://gitlab.com/MysteryBlokHed/ls-proxy/-/pipelines) [![NPM Badge]](https://www.npmjs.com/package/ls-proxy) [![License Badge]](#license) | ||
It also has great IDE support thanks to it being written in TypeScript. | ||
You can also use it with vanilla JS with the Webpacked file (`ls-proxy.user.js`), | ||
which is useful to test it in the browser or while writing UserScripts. | ||
You can also use it with vanilla JS with the Webpacked file (`ls-proxy.user.js` or `ls-proxy.min.user.js`), | ||
which is useful to test it in the browser, when not using a JS bundler, or while writing UserScripts. | ||
@@ -46,2 +46,25 @@ Here's all it takes to store a stringifed JSON object in localStorage and automatically change it: | ||
The method above stores an entire serialized object in localStorage, | ||
meaning the entire object has to be stringified/parsed whenever a single key | ||
is affected. The method below stores each key individually instead: | ||
```typescript | ||
import { storeSeparate } from 'ls-proxy' | ||
const someInfo = storeObject( | ||
// The object to store | ||
{ | ||
aString: 'Hello, World!', | ||
aNumber: 123, | ||
aBoolean: true, | ||
aList: [1, '2', 3], | ||
}, | ||
// Optional; prefixes the stored keys with this ID | ||
{ id: 'someInfo' }, | ||
) | ||
someInfo.aNumber = 42 // Updates localStorage | ||
console.log(someInfo.aList) // Reads from localStorage | ||
``` | ||
## Documentation | ||
@@ -54,2 +77,12 @@ | ||
## storeObject vs storeSeparate | ||
`storeSeparate` will generally be faster than `storeObject` | ||
since there's less data being serialized/deserialized with each get/set, | ||
but there are still reasons to use `storeObject`. | ||
For example, if you want to use `validate` and `modify` (see documentation for config options), | ||
and you need the entire object for context. | ||
This can be the case if you need another key's value to validate the object, | ||
or if you want to modify multiple keys with a single set/get. | ||
## Use | ||
@@ -56,0 +89,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34999
780
218