<div align="center">
	<p>
		<a href="https://local-save.febkosq8.me/"><img src="https://github.com/febkosq8/local-save/blob/main/src/assets/local-save_transparent_trimmed.png?raw=true" width="400" alt="local-save" /></a>
	</p>
	<p>
		<a href="https://www.npmjs.com/package/@febkosq8/local-save">
		<img alt="NPM Version" src="https://img.shields.io/npm/v/%40febkosq8%2Flocal-save"></a>
		<a href="https://www.npmjs.com/package/@febkosq8/local-save">
		<img alt="NPM Downloads" src="https://img.shields.io/npm/dm/%40febkosq8%2Flocal-save"></a>
	</p>
</div>

`local-save` is a lightweight wrapper around IndexedDB for secure and structured client-side data storage. It provides encryption, expiration, and organized data storage across configurable categories.

---

#### Looking to experience how `local-save` can securely store and retrieve data?

👉 **[Try the Live Demo](https://local-save.febkosq8.me/)** 👈

## Features

- **Lightweight & Widespread Support:** No external dependencies and works in all modern browsers.
- **TypeScript Support :** Written in TypeScript for maximum type-safety and a great developer experience.
- **Encryption Support :** Uses [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) for secure data encryption with [AES-GCM](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#aes-gcm) algorithm.
- **Category Management :** Organize data into predefined categories.
- **Data Expiry :** Allows setting an expiration threshold for data and a method to clear expired data.
- **IndexedDB Management :** Handles IndexedDB, object store creation and IndexedDB versioning management automatically.
- **Logging :** Debug logs for detailed insights.

---

## Installation

```bash
npm install @febkosq8/local-save
# or
pnpm add @febkosq8/local-save
```

---

## Usage

### Configuration Options

| Option Key                    | Type       | Default        | Description                                                                                                                                            |
| ----------------------------- | ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **_dbName_**                  | `string`   | `"LocalSave"`  | Name of the IndexedDB database.                                                                                                                        |
| **_encryptionKey_**           | `string`   | `undefined`    | Encryption key that should be either [16, 24, or 32] characters to be used for AES-GCM encryption. If not provided, data will be stored in plain text. |
| **_categories_**              | `string[]` | `["userData"]` | Array of categories (Object store names).                                                                                                              |
| **_expiryThreshold_**         | `number`   | `2592000000`   | Data expiration threshold in milliseconds.                                                                                                             |
| **_blockedTimeoutThreshold_** | `number`   | `10000`        | Timeout in milliseconds before blocked IndexedDB open/delete requests fail.                                                                            |
| **_clearOnDecryptError_**     | `boolean`  | `true`         | Whether to clear a category if decryption fails.                                                                                                       |
| **_printLogs_**               | `boolean`  | `false`        | Whether to print logs (Debug and errors).                                                                                                              |

### Initialization

Create a new instance of `LocalSave` with the available configuration options

```typescript
import LocalSave from "@febkosq8/local-save";
...
const localSaveConfig = {
  encryptionKey: "MyRandEncryptKeyThatIs32CharLong", // Encryption key for data
  categories: ["userData", "settings"], // Define categories for data storage
  expiryThreshold: 14 * 24 * 60 * 60 * 1000, // Clear data older than 14 days (in ms)
  blockedTimeoutThreshold: 15000, // Wait up to 15s when IndexedDB requests are blocked
};
const localSave = new LocalSave(localSaveConfig);
```

### Storing data

Store data in a category

_Here `userData` is the category. The key is `user001` for the data `{ name: "John Doe", age: 30 }`._

```typescript
await localSave.set('userData', 'user001', { name: 'John Doe', age: 30 });
```

### List saved data keys in a category

List all keys of the data stored in a category

```typescript
const keys = await localSave.listKeys('userData');
console.log(keys); // ['user001']
```

### Fetching data

Fetch data from a category using the `category` and `key`

_The data will be returned along with the timestamp when it was stored. If the data is expired, it will return `null`._

```typescript
try {
    const localDataFetch = await localSave.get('userData', 'user001');
    const { timestamp, data } = localDataFetch;
    console.log(data); // { name: "John Doe", age: 30 }
    console.log(timestamp); // UNIX timestamp of calling `set` method
    //handle data here
} catch (error) {
    //Error will be thrown if the decryption fails
    //handle error here
}
```

### Removing data

Remove data using the `category` and `key`

```typescript
await localSave.remove('userData', 'user001');
```

### Clearing a category

Clear all data from a `category`

```typescript
await localSave.clear('userData');
```

### Clearing expired data

Clear all expired data from all categories

```typescript
// If a value is provided, it will override the default expiry threshold
// This can be placed on top of the file to clear expired data on page load
await localSave.expire(14 * 24 * 60 * 60 * 1000); // Clear data older than 14 days from all categories (in ms)
```

### Destroying the entire database

Clear all data from all categories and delete the database

```typescript
await localSave.destroy();
```

### Decrypting data manually

Decrypt data using the encryption key

This is useful when you want to decrypt the `DBItemEncryptedBase64` string manually without using the `get` method

```typescript
const decryptedData = await localSave.decryptData(DBItemEncryptedBase64);
```
