# @svnrnns/typed-expo-store

A lightweight, type-safe wrapper for [expo-secure-store](https://docs.expo.dev/versions/latest/sdk/securestore/) with optional **Zod** validation for React Native and Expo apps.

## Features

- **Type Safety**: Built for TypeScript with generic types
- **Optional Zod Validation**: Add schema validation when you need it
- **Async & Sync API**: Both sync and async methods available
- **Namespace Support**: Avoid key collisions with prefixed keys
- **Expiration Support**: Store values with a time-to-live (TTL)
- **Biometric Support**: Check if biometric authentication is available
- **Fallback Values**: Ensure a value of the expected type is always retrieved

## Installation

```bash
npm install @svnrnns/typed-expo-store zod
```

## Important

While you don't need to install `expo-secure-store` directly for this package to work, it's required for your project if you want to enable custom plugins like Android Shared Preferences backup.

```bash
npm install expo-secure-store
```

```json
// app.json (expo)
"plugins": [
  "expo-router",
  ["expo-secure-store", { "configureAndroidBackup": true }]
]
```

## Quick Start

### Basic Usage

Create a `TypedStore` instance:

```ts
import { TypedStore } from "@svnrnns/typed-expo-store";

const store = new TypedStore();

// Set a value
store.setItem("theme", "dark");
await store.setItemAsync("user", { id: 1, name: "John" });

// Get a value (returns null if not found)
const theme = store.getItem("theme"); // 'dark' | null
const user = await store.getItemAsync<{ id: number; name: string }>("user");

// Get with fallback (returns fallback if not found)
const locale = store.getItem("locale", "en"); // 'en' if not found

// Remove a value
await store.removeItemAsync("theme");

// Check if key exists
store.itemExists("theme"); // boolean
await store.itemExistsAsync("theme"); // boolean
```

### With Namespace

Use a namespace to prefix keys and avoid conflicts:

```ts
import { TypedStore } from "@svnrnns/typed-expo-store";

const store = new TypedStore("my-app");

store.setItem("theme", "dark");
// Stored as 'my-app:theme' in SecureStore
```

### With Zod Validation

Add a Zod schema as the third parameter for type-safe validation:

```ts
import { TypedStore } from "@svnrnns/typed-expo-store";
import { z } from "zod";

const store = new TypedStore();

// Get with Zod validation (returns fallback if not found or invalid)
const themeSchema = z.enum(["light", "dark"]);
const theme = store.getItem("theme", "light", themeSchema);
// Returns 'light' if key 'theme' is not found or fails validation

// Async version
const userSchema = z.object({ id: z.number(), name: z.string() });
const user = await store.getItemAsync("user", { id: 0, name: "" }, userSchema);
```

### With Expiration

Set values with a time-to-live (TTL) in milliseconds:

```ts
import { TypedStore } from "@svnrnns/typed-expo-store";

const store = new TypedStore();

// Expires in 1 hour
await store.setItemWithExpiration("token", "xxxx", 3600000);
```

### Standalone Functions

You can also import standalone functions:

```ts
import {
  getItem,
  getItemAsync,
  setItem,
  setItemAsync,
  setItemWithExpiration,
  itemExists,
  itemExistsAsync,
  removeItemAsync,
} from "@svnrnns/typed-expo-store";
```

## Examples

Store arrays or complex objects with Zod validation:

```ts
import { TypedStore } from "@svnrnns/typed-expo-store";
import { z } from "zod";

const store = new TypedStore();

await store.setItemAsync("filters", ["desc", "price"]);

const schema = z.array(z.string());
const filters = await store.getItemAsync("filters", [], schema);
// Returns ['desc', 'price'] or [] if invalid
```

## API Reference

### TypedStore

| Method                          | Signature                                                     | Description                                           |
| ------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------- |
| `setItem`                       | `setItem<T>(key: string, value: T): void`                     | Stores a value (sync).                                |
| `setItemAsync`                  | `setItemAsync<T>(key: string, value: T): Promise<void>`       | Stores a value (async).                               |
| `setItemWithExpiration`         | `setItemWithExpiration<T>(key, value, ttl): Promise<void>`    | Stores a value that expires after `ttl` ms.           |
| `getItem`                       | `getItem<T>(key: string): T \| null`                          | Retrieves a value (sync). Returns null if not found.  |
| `getItem`                       | `getItem<T>(key: string, fallback: T): T`                     | Retrieves a value with fallback (sync).               |
| `getItem`                       | `getItem<T>(key: string, fallback: T, schema: ZodType<T>): T` | Retrieves a value with Zod validation (sync).         |
| `getItemAsync`                  | `getItemAsync<T>(key: string): Promise<T \| null>`            | Retrieves a value (async). Returns null if not found. |
| `getItemAsync`                  | `getItemAsync<T>(key: string, fallback: T): Promise<T>`       | Retrieves a value with fallback (async).              |
| `getItemAsync`                  | `getItemAsync<T>(key, fallback, schema): Promise<T>`          | Retrieves a value with Zod validation (async).        |
| `removeItemAsync`               | `removeItemAsync(key: string): Promise<void>`                 | Removes a key (async).                                |
| `itemExists`                    | `itemExists(key: string): boolean`                            | Checks if a key exists (sync).                        |
| `itemExistsAsync`               | `itemExistsAsync(key: string): Promise<boolean>`              | Checks if a key exists (async).                       |
| `getNamespace`                  | `getNamespace(): string \| undefined`                         | Gets the current namespace.                           |
| `setNamespace`                  | `setNamespace(namespace: string): void`                       | Sets a new namespace.                                 |
| `canUseBiometricAuthentication` | `canUseBiometricAuthentication(): boolean`                    | Checks if biometric auth is available.                |
| `static isAvailableAsync`       | `isAvailableAsync(): Promise<boolean>`                        | Checks if SecureStore is available on device.         |

MIT License © 2025
