hybrid-webcache
Version:
Hybrid WebCache - A library that combines `localStorage`, `IndexedDB`, `SessionStorage` and `Memory` to provide a high-performance hybrid cache with multi-instance synchronization support.
874 lines (873 loc) • 29.7 kB
JavaScript
import { get as _get, set as _set, unset as _unset } from "lodash";
import { StorageFactory } from "./StorageFactory";
import { StorageEngine } from "./types";
import { Utils } from "./utils";
/**
* @internal
*/
const defaultOptions = {
ttl: { seconds: 0, minutes: 0, hours: 1, days: 0 },
removeExpired: true,
storage: StorageEngine.Auto,
};
/**
* Represents a hybrid web cache that supports both asynchronous and synchronous
* operations for storing, retrieving, and managing key-value pairs with optional
* time-to-live (TTL) settings.
*
* The cache can automatically remove expired entries
* and supports various storage engines.
*
* Provides methods for setting, getting,
* checking existence, and unsetting values, as well as resetting the cache with
* new data. Includes utility functions for converting TTL and calculating storage
* size.
* @author Heliomar Marques
* @example
*
* Basic Usage with Default Options (storage: Auto, ttl: 1 hour)
* ```ts
* import { HybridWebCache, StorageEngine } from 'hybrid-webcache';
*
* const cache = new HybridWebCache();
*
* await cache.set('sessionToken', 'abc123');
* const tokenData = await cache.get<string>('sessionToken');
* console.log(`Token: ${tokenData?.value}`); // Output: Token: abc123
* console.log(`Is Expired: ${tokenData?.isExpired}`); // Output: Is Expired: false
* ```
* @example
* Creating an instance with custom options (e.g., IndexedDB, 10-minute TTL)
* ```ts
* import { HybridWebCache, StorageEngine } from 'hybrid-webcache';
*
* // Note: For IndexedDB, remember to call .init() if you plan to use synchronous methods
* const indexedDBCache = new HybridWebCache('myAppCache', {
* storage: StorageEngine.IndexedDB,
* ttl: { minutes: 10 },
* removeExpired: true,
* });
*
* await indexedDBCache.init(); // Initialize IndexedDB to load memory cache for sync operations
* //Setting and Getting Nested Data
* await indexedDBCache.set('user.profile.firstName', 'John', { hours: 1 });
* indexedDBCache.setSync('user.profile.lastName', 'Doe'); // Uses instance's default TTL (10 minutes)
* indexedDBCache.setSync(['user', 'profile', 'age'], 30); // Array KeyPath
*
* const userData = await indexedDBCache.get('user.profile');
* console.log(userData?.value); // Output: { firstName: 'John', lastName: 'Doe', age: 30 }
* const firstNameData = indexedDBCache.getSync('user.profile.firstName');
* console.log(firstNameData?.value); // Output: John
*
* // Checking for Key Existence
* const hasUser = await indexedDBCache.has('user.profile.firstName');
* console.log(`Has user first name: ${hasUser}`); // Output: Has user first name: true
*
* const hasNonExistentKey = indexedDBCache.hasSync('non.existent.key');
* console.log(`Has non-existent key: ${hasNonExistentKey}`); // Output: Has non-existent key: false
*
* // Unsetting Data (Partial and Full)
* const complexObject = {
* theme: 'dark',
* settings: {
* language: 'en-US',
* notifications: { email: true, sms: false }
* }
* };
* await indexedDBCache.set('appConfig', complexObject);
*
* // Unset a nested property
* await indexedDBCache.unset('appConfig.settings.notifications.sms');
* const updatedAppConfig = await indexedDBCache.get('appConfig');
* console.log(updatedAppConfig?.value);
* // Output: { theme: 'dark', settings: { language: 'en-US', notifications: { email: true } } }
*
* // Unset an array element (sets to null)
* indexedDBCache.unsetSync('appConfig.items[1]');
* const updatedItems = indexedDBCache.getSync('appConfig.items');
* console.log(updatedItems?.value); // Output: ['apple', null, 'orange']
*
* // Unset the entire 'appConfig' key
* await indexedDBCache.unset('appConfig');
* const appConfigAfterUnset = await indexedDBCache.get('appConfig');
* console.log(appConfigAfterUnset); // Output: undefined
*
* // Retrieving All Data
* await indexedDBCache.set('product1', { id: 1, name: 'Laptop' });
* await indexedDBCache.set('product2', { id: 2, name: 'Mouse' });
*
* const allItemsMap = await indexedDBCache.getAll();
* console.log(allItemsMap);
* /* Output:
* Map(2) {
* 'product1' => { value: { id: 1, name: 'Laptop' }, expiresAt: ..., isExpired: false },
* 'product2' => { value: { id: 2, name: 'Mouse' }, expiresAt: ..., isExpired: false }
* }
* *\/
*
* const allItemsJson = indexedDBCache.getJsonSync();
* console.log(allItemsJson);
* /* Output:
* { product1: { id: 1, name: 'Laptop' },
* product2: { id: 2, name: 'Mouse' }
* } *\/
*
* // Resetting the Cache
* await indexedDBCache.resetWith({
* user: { id: 'user123', status: 'active' },
* app: { version: '1.0.0' }
* }, { minutes: 5 }); // New TTL for reset
*
* const resetData = await indexedDBCache.getJson();
* console.log(resetData);
* /* Output:
* {
* user: { id: 'user123', status: 'active' },
* app: { version: '1.0.0' }
* } *\/
*
* // Getting Cache Info
* const cacheInfo = indexedDBCache.info;
* console.log(cacheInfo);
* /* Output:
* {
* dataBase: 'myAppCache',
* size: 'XXb', // e.g., '120b'
* options: {
* ttl: 300000, // 5 minutes in ms
* removeExpired: true,
* storage: 2 // StorageEngine.IndexedDB
* }
* } *\/
* ```
*
* @category Core
*/
export class HybridWebCache {
/**
* Basename
* @private
*/
baseName;
/**
* The options for the Cache.
* @type {@link Options}
* @private
*/
options;
/** @ignore */
storageBase;
/**
* Constructor for Hybrid WebCache.
*
* To reset the cache, use [`resetWith()`|`resetWithSync()`].
*
* _**Note:**_ For `StorageType.IndexedDB`, remember to call .init() if you plan to use synchronous methods
*
* @param {string} [baseName='HybridWebCache'] - The base name of the cache.
* @param {Partial<Options>} options
* @default
* ```ts
* //options
* {
* ttl: { seconds: 0, minutes: 0, hours: 1, days: 0 },
* removeExpired: true,
* storage: StorageType.Auto
* }
* ```
*/
constructor(baseName = "HybridWebCache", options) {
this.baseName = baseName;
this.options = { ...defaultOptions, ...options };
this.storageBase = this.determineStorageEngine(this.options.storage);
this.options.storage = this.storageBase.type;
}
determineStorageEngine(storage) {
return StorageFactory.createStorage(storage, this.baseName);
}
createKey(keyPath) {
return Utils.getKey(keyPath);
}
prepareDataSet(value, ttl = this.options.ttl) {
const ttlMs = Utils.convertTTLToMilliseconds(ttl);
const expiresAt = ttlMs > 0 ? Date.now() + ttlMs : 0;
const data = { value, expiresAt };
return { data };
}
/**
* Initializes the memory cache
*
* This method is only necessary to use the synchronous functions of the IndexedDB strategy.
*
* @return A promise that resolves when the local storage is initialized.
*
* @example
*
* ```ts
* const cache = new HybridWebCache("CacheDB", {storage: StorageEngine.IndexedDB});
* await cache.init();
* ```
*
* @category Init Method
*/
async init() {
await this.storageBase.init();
}
/**
* Sets the value for a given keyPath in the storage engine.
*
* If the keyPath already exists, its value is updated with the provided
* value. If the keyPath does not exist, a new entry is created with the
* provided TTL.
*
* @template {@link ValueType} T - The type of the value being stored.
* @param {@link KeyPath} keyPath - The keyPath to be stored.
* @param {@link ValueType} value - The value to be stored.
* @param {@link TTL} ttl - Optional TTL settings for the stored value. Defaults to
* the instance's configured TTL.
*
* @example
*
* Change the value at `color.name` to `sapphire`.
* ```ts
* // Given:
* {
* "color": {
* "name": "cerulean",
* "code": {
* "rgb": [0, 179, 230],
* "hex": "#003BE6"
* }
* }
* }
*
* const cache = new HybridWebCache();
* await cache.set('color.name', 'sapphire');
* ```
* @example
*
* Set the value of `color.hue` to `bluish`.
* ```ts
* const cache = new HybridWebCache();
* await cache.set(['color', 'hue'], 'bluish);
* ```
* @example
*
* Change the value of `color.code`.
* ```ts
* const cache = new HybridWebCache();
* await cache.set('color.code', { rgb: [16, 31, 134], hex: '#101F86' });
* ```
*
* @category Set Methods
*/
async set(keyPath, value, ttl = this.options.ttl) {
if (keyPath === undefined || keyPath === null) {
throw new Error("KeyPath cannot be undefined or null.");
}
const key = this.createKey(keyPath);
const data = await this.storageBase.get(key);
const obj = data?.value || {};
_set(obj, keyPath, value);
const dataSet = this.prepareDataSet(obj, ttl);
return this.storageBase.set(key, dataSet.data);
}
/**
* Synchronous version of set.
*
* @template {@link ValueType} T - The type of the value being stored.
* @param {@link KeyPath} keyPath - The keyPath to be stored.
* @param {@link ValueType} value - The value to be stored.
* @param {@link TTL} ttl - Optional TTL settings for the stored value. Defaults to
* the instance's configured TTL.
*
* @example
*
* Change the value at `color.name` to `sapphire`.
* ```ts
* // Given:
* {
* "color": {
* "name": "cerulean",
* "code": {
* "rgb": [0, 179, 230],
* "hex": "#003BE6"
* }
* }
* }
* cache.setSync('color.name', 'sapphire');
* ```
* @example
*
* Set the value of `color.hue` to `bluish`.
* ```ts
* cache.setSync(['color', 'hue'], 'bluish);
* ```
* @example
*
* Change the value of `color.code`.
* ```ts
* cache.setSync('color.code', { rgb: [16, 31, 134], hex: '#101F86' });
* ```
*
* @category Set Methods
*/
setSync(keyPath, value, ttl = this.options.ttl) {
if (keyPath === undefined || keyPath === null) {
throw new Error("KeyPath cannot be undefined or null.");
}
const key = this.createKey(keyPath);
const data = this.storageBase.getSync(key);
const obj = data?.value || {};
_set(obj, keyPath, value);
const dataSet = this.prepareDataSet(obj, ttl);
this.storageBase.setSync(key, dataSet.data);
}
/**
* Retrieves the value associated with the specified keyPath from the storage engine.
*
* If the value is found, it returns an object containing the value, expiration time,
* and expiration status. If the value is expired and the `removeExpired` flag is set
* to true, the expired value is removed from storage and `undefined` is returned.
*
* @template T - The type of the value being retrieved.
* @param {@link KeyPath} keyPath - The path to the key whose value should be retrieved.
* @param {boolean} removeExpired - A flag indicating whether to remove the key if its value
* is expired. Defaults to the instance's configured setting.
* @returns A promise that resolves to an object containing the value and its metadata,
* or `undefined` if the value does not exist or is expired and removed.
*
* @example
*
* Get the value at `color.name`.
* ```ts
* // Given:
* {
* "color": {
* "name": "cerulean",
* "code": {
* "rgb": [0, 179, 230],
* "hex": "#003BE6"
* }
* }
* }
* const cache = new HybridWebCache();
* const value = await cache.get('color.name');
* // => "cerulean"
* ```
* @example
*
* Get the value at `color.code.hex`.
* ```ts
* const hex = await cache.get('color.color.hex');
* // => "#003BE6"
* ```
* @example
*
* Get the value at `color.hue`.
* ```ts
* const value = await cache.get(['color', 'hue']);
* // => undefined
* ```
* @example
*
* Get the value at `color.code.rgb[1]`.
* ```ts
* const value = await cache.get('color.code.rgb[1]');
* // => 179
* ```
*
* @category Get Methods
*/
async get(keyPath, removeExpired = this.options.removeExpired) {
if (keyPath === undefined || keyPath === null) {
throw new Error("KeyPath cannot be undefined or null.");
}
const key = this.createKey(keyPath);
const data = await this.storageBase.get(key);
if (data) {
const value = _get(data.value, keyPath);
if (value === undefined) {
return;
}
const isExpired = Utils.isExpired(data.expiresAt);
if (removeExpired && isExpired) {
await this.unset(keyPath);
return;
}
return {
value,
expiresAt: data.expiresAt,
isExpired,
};
}
return;
}
/**
* Synchronous version of get.
*
* Retrieves the value associated with the specified keyPath from the storage engine.
*
* If the value is found, it returns an object containing the value, expiration time,
* and expiration status. If the value is expired and the `removeExpired` flag is set
* to true, the expired value is removed from storage and `undefined` is returned.
*
* @template T - The type of the value being retrieved.
* @param {@link KeyPath} keyPath - The path to the key whose value should be retrieved.
* @param {boolean} removeExpired - A flag indicating whether to remove the key if its value
* is expired. Defaults to the instance's configured setting.
* @returns An object containing the value and its metadata, or `undefined` if the
* value does not exist or is expired and removed.
* @example
*
* Get the value at `color.name`.
* ```ts
* // Given:
* {
* "color": {
* "name": "cerulean",
* "code": {
* "rgb": [0, 179, 230],
* "hex": "#003BE6"
* }
* }
* }
*
* const value = cache.getSync('color.name');
* // => "cerulean"
* ```
* @example
*
* Get the value at `color.code.hex`.
* ```ts
* const hex = cache.getSync('color.color.hex');
* // => "#003BE6"
* ```
* @example
*
* Get the value at `color.hue`.
* ```ts
* const value = cache.getSync(['color', 'hue']);
* // => undefined
* ```
* @example
*
* Get the value at `color.code.rgb[1]`.
* ```ts
* const value = cache.getSync('color.code.rgb[1]');
* // => 179
* ```
*
* @category Get Methods
*/
getSync(keyPath, removeExpired = this.options.removeExpired) {
if (keyPath === undefined || keyPath === null) {
throw new Error("KeyPath cannot be undefined or null.");
}
const key = this.createKey(keyPath);
const data = this.storageBase.getSync(key);
if (data) {
const value = _get(data.value, keyPath);
if (value === undefined) {
return;
}
const isExpired = Utils.isExpired(data.expiresAt);
if (removeExpired && isExpired) {
this.unsetSync(keyPath);
return;
}
return {
value,
expiresAt: data.expiresAt,
isExpired,
};
}
return;
}
/**
* Retrieves all key-value pairs from the storage engine.
*
* If the `removeExpired` flag is set to true, expired values are removed from storage
* before being returned.
*
* @param {boolean} removeExpired - A flag indicating whether to remove expired values from storage.
* Defaults to the instance's configured setting.
* @returns A map of key-value pairs, where each value is an object containing the value,
* expiration time, and expiration status. If no values are found or if all values
* are expired and removed, `null` is returned.
*
* @category Get Methods
*/
async getAll(removeExpired = this.options.removeExpired) {
const allItems = await this.storageBase.getAll();
if (!allItems) {
return null;
}
const result = new Map();
for (const [key, data] of allItems) {
const [iKey, iValue] = Object.entries(data.value ?? { key, value: null })[0];
// Check if the item is expired
const isExpired = Utils.isExpired(data.expiresAt);
// If `removeExpired` is true and the item is expired, remove it and skip adding to result
if (removeExpired && isExpired) {
await this.unset(iKey);
continue;
}
result.set(iKey, {
value: iValue,
expiresAt: data.expiresAt,
isExpired,
});
}
return Promise.resolve(result.size > 0 ? result : null);
}
/**
* Synchronously retrieves all items from storage as a map of key-value pairs,
* where each value is an object containing the value, expiration time, and expiration status.
*
* If the `removeExpired` flag is set to true, expired values are removed from storage
* before being returned.
*
* @param {boolean} removeExpired - A flag indicating whether to remove expired values from storage.
* Defaults to the instance's configured setting.
* @returns A map of key-value pairs, where each value is an object containing the value,
* expiration time, and expiration status. If no values are found or if all values
* are expired and removed, `null` is returned.
*
* @category Get Methods
*/
getAllSync(removeExpired = this.options.removeExpired) {
const allItems = this.storageBase.getAllSync();
if (!allItems) {
return null;
}
const result = new Map();
for (const [key, data] of allItems) {
const [iKey, iValue] = data.value ? Object.entries(data.value)[0] : [key, null];
// Check if the item is expired
const isExpired = Utils.isExpired(data.expiresAt);
// If `removeExpired` is true and the item is expired, remove it and skip adding to result
if (removeExpired && isExpired) {
this.unsetSync(iKey);
continue;
}
result.set(iKey, {
value: iValue,
expiresAt: data.expiresAt,
isExpired,
});
}
return result.size > 0 ? result : null;
}
/**
* Asynchronously retrieves all key-value pairs from the storage as a JSON object.
*
* If the `removeExpired` flag is set to true, expired values are removed from storage
* before being included in the result.
*
* @param {boolean} removeExpired - A flag indicating whether to remove expired values from storage.
* Defaults to the instance's configured setting.
* @returns A promise that resolves to a JSON object containing all key-value pairs.
* If no items are found or all items are expired and removed, `null` is returned.
*
* @category Get Methods
*/
async getJson(removeExpired = this.options.removeExpired) {
const allValues = {};
const allItems = await this.getAll(removeExpired);
if (!allItems) {
return null;
}
for (const [key, data] of allItems) {
if (data && data.value !== undefined) {
allValues[key] = data.value;
}
}
return allValues;
}
/**
* Synchronously retrieves all key-value pairs from the storage as a JSON object.
*
* If the `removeExpired` flag is set to true, expired values are removed from storage
* before being included in the result.
*
* @param {boolean} removeExpired - A flag indicating whether to remove expired values from storage.
* Defaults to the instance's configured setting.
* @returns A JSON object containing all key-value pairs. If no items are found or all
* items are expired and removed, `null` is returned.
*
* @category Get Methods
*/
getJsonSync(removeExpired = this.options.removeExpired) {
const allValues = {};
const allItems = this.getAllSync(removeExpired);
if (!allItems) {
return null;
}
for (const [key, data] of allItems) {
if (data && data.value !== undefined) {
allValues[key] = data.value;
}
}
return allValues;
}
/**
* Checks if the given key path exists.
*
* _For sync method, use_ [`hasSync()`].
*
* @param {@link KeyPath} keyPath The key path to check.
* @returns A promise which resolves to `true` if the `keyPath` exists, else `false`.
* @example
*
* Check if the value at `color.name` exists.
* ```ts
* // Given:
* {
* "color": {
* "name": "cerulean",
* "code": {
* "rgb": [0, 179, 230],
* "hex": "#003BE6"
* }
* }
* }
*
* const exists = await cache.has('color.name');
* // => true
* ```
* @example
*
* Check if the value at `color.hue` exists.
* ```ts
* const exists = await cache.has(['color', 'hue']);
* // => false
* ```
* @example
*
* Check if the value at `color.code.rgb[1]` exists.
* ```ts
* const exists = await cache.has(color.code.rgb[1]);
* // => true
* ```
*
* @category Has Methods
*/
async has(keyPath) {
if (keyPath === undefined || keyPath === null) {
throw new Error("KeyPath cannot be undefined or null.");
}
const key = this.createKey(keyPath);
if (key === keyPath.toString()) {
return this.storageBase.has(key);
}
const data = await this.get(keyPath);
return data !== undefined && data?.value !== null;
}
/**
* Checks if the given key path exists.
*
* _For async method, use_ [`has()`].
*
* @param {@link KeyPath} keyPath The key path to check.
* @returns `true` if the `keyPath` exists, else `false`.
* @example
*
* Check if the value at `color.name` exists.
* ```ts
* // Given:
* {
* "color": {
* "name": "cerulean",
* "code": {
* "rgb": [0, 179, 230],
* "hex": "#003BE6"
* }
* }
* }
*
* const exists = cache.hasSync('color.name');
* // => true
* ```
* @example
*
* Check if the value at `color.hue` exists.
* ```ts
* const exists = cache.hasSync(['color', 'hue']);
* // => false
* ```
* @example
*
* Check if the value at `color.code.rgb[1]` exists.
* ```ts
* const exists = cache.hasSync(color.code.rgb[1]);
* // => true
* ```
*
* @category Has Methods
*/
hasSync(keyPath) {
if (keyPath === undefined || keyPath === null) {
throw new Error("KeyPath cannot be undefined or null.");
}
const key = this.createKey(keyPath);
if (key === keyPath.toString()) {
return this.storageBase.hasSync(key);
}
const data = this.getSync(keyPath);
return data !== undefined && data?.value !== null;
// return data !== undefined && data?.value !== null;
// return _has(data?.value, keyPath);
}
async unset(keyPath) {
if (this.storageBase.length === 0)
return false;
if (keyPath) {
const key = this.createKey(keyPath);
const data = await this.storageBase.get(key);
if (data) {
if (_unset(data.value, keyPath)) {
if (Object.keys(data.value || {}).length > 0) {
//update
await this.storageBase.set(key, data);
return true;
}
}
return this.storageBase.unset(key);
}
return false;
}
return this.storageBase.unset();
}
unsetSync(keyPath) {
if (this.storageBase.length === 0)
return false;
if (keyPath) {
const key = this.createKey(keyPath);
const data = this.storageBase.getSync(key);
if (data) {
if (_unset(data.value, keyPath)) {
if (Object.keys(data.value || {}).length > 0) {
//update
this.storageBase.setSync(key, data);
return true;
}
}
return this.storageBase.unsetSync(key);
}
return false;
}
return this.storageBase.unsetSync();
}
/**
* Resets the storage with the provided key-value pairs and optional TTL.
*
* This method first clears all existing entries in the storage engine.
* It then iterates over the provided key-value pairs, setting each one
* in the storage with the specified TTL. If no TTL is provided, the
* default TTL from the options is used.
*
* @template T - The type of values being stored.
* @param {@link KeyPath} keyValues - An object containing key-value pairs to be stored.
* @param {@link TTL} ttl - Optional TTL settings for the stored values. Defaults to
* the instance's configured TTL.
* @returns A promise that resolves when all key-value pairs have been
* set in the storage.
*
* @category Reset Data Methods
*/
async resetWith(keyValues, ttl = this.options.ttl) {
await this.storageBase.unset();
const promises = Object.entries(keyValues).map(([key, value]) => {
const obj = {};
_set(obj, key, value);
const dataSet = this.prepareDataSet(obj, ttl);
return this.storageBase.set(this.createKey(key), dataSet.data);
});
await Promise.all(promises);
}
/**
* Resets the storage with the provided key-value pairs and optional TTL.
*
* This method first clears all existing entries in the storage engine.
* It then iterates over the provided key-value pairs, setting each one
* in the storage with the specified TTL. If no TTL is provided, the
* default TTL from the options is used.
*
* @template T - The type of values being stored.
* @param {@link KeyPath} keyValues - An object containing key-value pairs to be stored.
* @param {@link TTL} ttl - Optional TTL settings for the stored values. Defaults to
* the instance's configured TTL.
*
* @category Reset Data Methods
*/
resetWithSync(keyValues, ttl = this.options.ttl) {
this.storageBase.unsetSync();
Object.entries(keyValues).forEach(([key, value]) => {
const obj = {};
_set(obj, key, value);
const dataSet = this.prepareDataSet(obj, ttl);
this.storageBase.setSync(this.createKey(key), dataSet.data);
});
}
/**
* Retrieves the number of items currently stored in the cache.
*
* @returns The count of items in the storage. *
* @category Auxiliary Methods
*/
get length() {
return this.storageBase.length;
}
/**
* Retrieves the total number of bytes used by the cache in the storage.
*
* @returns The total bytes used by the cache.
* @category Auxiliary Methods
*/
get bytes() {
return this.storageBase.bytes;
}
/**
* Provides information about the current cache.
*
* @returns An object containing:
* - `dataBase`: The name of the database used by the cache.
* - `size`: The calculated storage size in bytes represented as a string.
* - `options`: The current cache options including TTL converted to milliseconds.
*
* ```ts
* {
* dataBase: 'myAppCache',
* size: 'XXb', // e.g., '120b'
* options: {
* ttl: 300000, // 5 minutes in ms
* removeExpired: true,
* storage: 2 // StorageEngine.IndexedDB
* }
* }
* ```
*
* @category Auxiliary Methods
*/
get info() {
const size = Utils.calculateStorageSize(this.storageBase.bytes);
return {
dataBase: this.baseName,
size,
options: {
...this.options,
ttl: Utils.convertTTLToMilliseconds(this.options.ttl),
},
};
}
/**
* Returns the type of storage engine used by the cache.
*
* @returns The type of storage engine used by the cache.
* @category Auxiliary Methods
*/
get storageType() {
return this.storageBase.type;
}
}