@figliolia/typed-storage
v1.0.1
Published
A type safe wrapper for browser storage API's
Downloads
19
Maintainers
Readme
Typed Storage
A type-safe wrapper around the browser's LocalStorage
and SessionStorage
API's. The motivation behind this package is two fold:
- When working with large teams, ensuring that key-names are consistent and follow certain conventions can be combersome
- Ensuring that only values of a certain type are stored for each key can require can be difficult because the API's require string-values when storing data
The TypedStorage
API allows you define a schema for your data and restricts storing data to only that which matches your key's corresponding value type.
Installation
npm i @figliolia/typed-storage
# or
yarn add @figliolia/typed-storage
Basic Usage
Defining Your API
import { TypedStorage } from "@figliolia/typed-storage";
export interface Schema {
JWT: string;
userId: number;
shoppingCart: { item: string, price: number }[];
}
const LocalStorage = new TypedStorage<Schema>(localStorage);
// or
const SessionStorage = new TypedStorage<Schema>(sessionStorage);
Using Your API
When using your TypedStorage
instances, your data-type is preserved when setting and getting:
import { LocalStorage } from "./path/to/myLocalStorage";
LocalStorage.setItem("shoppingCart", [
{item: "Bananas", price: 3.00 },
{item: "Apples", price: 2.50 },
// Stringified under the hood
]);
const cart = LocalStorage.getItem("shoppingCart"); // parsed under the hood
// { item: string, price: number }[] | null
const cart = LocalStorage.getItem("cart"); // mispelled key
// Fails typescript validation!
Supported Data Types
The TypedStorage
API supports storing any data-type that is JSON-valid. For keys with values of type object
or array
, JSON.parse()
will be used to deserialize your data upon retreival.
For keys with values of type string
or number
, best effort parsing is used to determine the correct type on retrieval. For example if the value being retrieved can be safely converted to an integer, float, or BigInt
, it will be. Otherwise the value will be returned as a string.
For instances where your key-value pair requires a customized serialization or deserialization technique, you can instantiate your TypedStorage
along with your deserializer mapped to your key:
import { TypedStorage } from "@figliolia/typed-storage";
interface Schema {
meyKey: Map<string, number>; // (invalid JSON)
}
const MyStorage = new TypedStorage<Schema>(localStorage, {
meyKey: {
// Convert map to object and stringify it for storage
serialize: (map) => {
const obj: Record<string, number> = {};
for(const [key, value] of map) {
obj[key] = value;
}
return JSON.stringify(map);
},
// Convert stored object back into a Map for runtime usage
deserialize: (map) => {
const obj = JSON.parse(map);
const result = new Map();
for(const key in obj) {
result.set(key, obj[key]);
}
return result;
}
}
});
Serializers can also be used in instances where you wish to handle numeric values as strings
import { TypedStorage } from "@figliolia/typed-storage";
interface Schema {
floatValue: string; // Handle float as strings
}
const MyStorage = new TypedStorage<Schema>(localStorage, {
floatValue: {
// Prevent float-like string from being returned as a number
deserialize: (floatValue) => floatValue;
}
})
Because TypedStorage
parses using a best-effort interpretation of the value, if your string contains only numeric characters, it'll be parsed as a number regardless of your schema definition.
If you run into a case such as this and you wish to preserve string-types for numeric values, provide a serialize
method for that key that simply returns the value as is.
Advanced Usage
This library also provides an enhancement to the TypedStorage
API called LiveStorage
. It works identically to TypedStorage
with the exception that LiveStorage
allows you to synchronize your application logic with the data you store:
import { LiveStorage } from "@figliolia/typed-storage";
export interface Schema {
JWT: string;
userId: number;
shoppingCart: { item: string, price: number }[];
}
const LocalStorage = new TypedStorage<Schema>(localStorage);
// or
const SessionStorage = new TypedStorage<Schema>(sessionStorage);
// Let's update our UI whenever our `shoppingCart` changes
const currency = new Intl.NumberFormat('en-us', {
style: 'currency',
currency: 'USD'
});
const checkoutUI = document.getElementById("checkout");
LocalStorage.on("shoppingCart", cart => {
if(cart === null) {
// the key was deleted
checkoutUI.textContent = currency.format(0.00);
return;
}
// Update your checkout UI's total cost $
const newPrice = cart.reduce((acc, next) => {
acc += next.price;
return acc;
}, 0);
checkoutUI.textContent = currency.format(newPrice);
});
In the example above, we'll update our checkout UI
whenever the user's shopping cart is updated.
When is LiveStorage the better idea?
Use LiveStorage
in areas where your business logic depends heavily on reads/writes to storage. If your application is performing read/writes in logic that spans multiple features or modules, you may find the that LiveStorage
API allows you to write more centralized logic in otherwise complex scenarios.