> ## Documentation Index
> Fetch the complete documentation index at: https://stratasync.blode.md/llms.txt
> Use this file to discover all available pages before exploring further.

# @stratasync/storage-idb

Implements `StorageAdapter` using IndexedDB. Persists model data, the outbox, sync metadata, partial indexes, and sync actions for offline-first operation and instant startup.

## What it provides

- **`createIndexedDbStorage`**: factory function that creates a configured `StorageAdapter` instance
- **`IndexedDbStorageAdapter`**: the class implementing the full `StorageAdapter` interface
- **`DatabaseManager`**: manages a registry of workspace databases for multi-user/multi-version support
- **Store operations**: typed functions for model rows, metadata, outbox transactions, partial indexes, and sync actions
- **Schema migrations**: automatic IndexedDB schema upgrades when the model schema changes
- **Store name utilities**: deterministic hashing for store and database names

## Installation

```bash
npm install @stratasync/storage-idb
```

`@stratasync/storage-idb` has no peer dependencies. It uses the [`idb`](https://github.com/nicolo-ribaudo/idb) library (^8.0.0) internally for a promise-based IndexedDB wrapper.

## Quick start

```ts
import { createSyncClient } from "@stratasync/client";
import { createIndexedDbStorage } from "@stratasync/storage-idb";

const storage = createIndexedDbStorage();

const client = createSyncClient({
  storage,
  // ... transport, reactivity
});
```

The sync client opens the adapter during initialization. Call `open()` manually only when using the adapter outside the sync client.

## StorageAdapter interface

### Lifecycle

| Method  | Signature                                    | Description                                                      |
| ------- | -------------------------------------------- | ---------------------------------------------------------------- |
| `open`  | `(options: StorageOptions) => Promise<void>` | Opens the database, creates object stores, initializes metadata. |
| `close` | `() => Promise<void>`                        | Closes all database connections.                                 |
| `clear` | `() => Promise<void>`                        | Clears all data from all stores (for testing or logout).         |

### Model row operations

| Method       | Signature                                                                | Description                                                                 |
| ------------ | ------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| `get`        | `<T>(modelName: string, id: string) => Promise<T \| null>`               | Gets a single row by primary key.                                           |
| `getAll`     | `<T>(modelName: string) => Promise<T[]>`                                 | Gets all rows for a model.                                                  |
| `put`        | `<T>(modelName: string, row: T) => Promise<void>`                        | Inserts or updates a row.                                                   |
| `delete`     | `(modelName: string, id: string) => Promise<void>`                       | Deletes a row by primary key.                                               |
| `getByIndex` | `<T>(modelName: string, indexName: string, key: string) => Promise<T[]>` | Gets rows by an indexed field value.                                        |
| `count`      | `(modelName: string) => Promise<number>`                                 | Counts rows in a model store.                                               |
| `writeBatch` | `(ops: BatchOperation[]) => Promise<void>`                               | Executes multiple put/delete operations atomically in a single transaction. |

### Metadata operations

| Method                | Signature                                                  | Description                                                |
| --------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- |
| `getMeta`             | `() => Promise<StorageMeta>`                               | Gets the sync metadata (lastSyncId, schemaHash, and more). |
| `setMeta`             | `(updates: Partial<StorageMeta>) => Promise<void>`         | Updates sync metadata fields.                              |
| `getModelPersistence` | `(modelName: string) => Promise<ModelPersistenceMeta>`     | Gets persistence status for a model.                       |
| `setModelPersistence` | `(modelName: string, persisted: boolean) => Promise<void>` | Sets whether a model's data is fully persisted locally.    |

### Outbox operations

| Method                    | Signature                                                              | Description                                     |
| ------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------- |
| `getOutbox`               | `() => Promise<Transaction[]>`                                         | Gets all pending transactions from the outbox.  |
| `addToOutbox`             | `(tx: Transaction) => Promise<void>`                                   | Adds a transaction to the outbox.               |
| `removeFromOutbox`        | `(clientTxId: string) => Promise<void>`                                | Removes a transaction by client transaction ID. |
| `updateOutboxTransaction` | `(clientTxId: string, updates: Partial<Transaction>) => Promise<void>` | Updates fields on an outbox transaction.        |

### Partial index operations

| Method            | Signature                                                                       | Description                                     |
| ----------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- |
| `hasPartialIndex` | `(modelName: string, indexedKey: string, keyValue: string) => Promise<boolean>` | Checks if a partial index key has been fetched. |
| `setPartialIndex` | `(modelName: string, indexedKey: string, keyValue: string) => Promise<void>`    | Marks a partial index key as fetched.           |

### Sync action operations

| Method             | Signature                                                         | Description                                       |
| ------------------ | ----------------------------------------------------------------- | ------------------------------------------------- |
| `addSyncActions`   | `(actions: SyncAction[]) => Promise<void>`                        | Persists sync actions for local replay/debugging. |
| `getSyncActions`   | `(afterSyncId?: number, limit?: number) => Promise<SyncAction[]>` | Reads stored sync actions after a given sync ID.  |
| `clearSyncActions` | `() => Promise<void>`                                             | Clears all stored sync actions.                   |

## Configuration

### StorageOptions

Passed to `open()` by the sync client:

```ts
interface StorageOptions {
  /** Database name override (auto-generated if omitted) */
  name?: string;
  /** Client version used to derive the workspace database name */
  version?: number;
  /** Logged-in user ID (used to derive the workspace database name) */
  userId?: string;
  /** Per-user version used to derive the workspace database name */
  userVersion?: number;
  /** Schema definition or registry snapshot */
  schema?: SchemaDefinition | ModelRegistrySnapshot;
}
```

When you don't provide `name`, the adapter computes the database name deterministically from `userId`, `version`, and `userVersion` using a hash function (for example, `ss_<hash>`).

## Types

### StorageMeta

Sync metadata stored in the `_meta` object store.

```ts
interface StorageMeta {
  schemaHash?: string;
  lastSyncId: number;
  firstSyncId?: number;
  subscribedSyncGroups?: string[];
  clientId?: string;
  bootstrapComplete?: boolean;
  lastSyncAt?: number;
  databaseVersion?: number;
  updatedAt?: number;
}
```

### ModelPersistenceMeta

Per-model persistence tracking.

```ts
interface ModelPersistenceMeta {
  modelName: string;
  persisted: boolean;
  updatedAt?: number;
}
```

### BatchOperation

Used with `writeBatch` for atomic multi-store writes.

```ts
type BatchOperationType = "put" | "delete";

interface BatchOperation {
  type: BatchOperationType;
  modelName: string;
  id?: string; // Required for "delete"
  data?: Record<string, unknown>; // Required for "put"
}
```

### PartialIndexEntry

Tracks which partial index keys have been fetched from the server.

```ts
interface PartialIndexEntry {
  modelName: string;
  indexedKey: string;
  keyValue: string;
  updatedAt?: number;
}
```

## Internal database structure

<details>
<summary>Expand to see the internal IndexedDB database layout</summary>

The adapter manages multiple IndexedDB databases.

### Workspace database

The main database contains these object stores:

| Store          | Key               | Description                                                                        |
| -------------- | ----------------- | ---------------------------------------------------------------------------------- |
| `_meta`        | `string`          | Sync metadata and per-model persistence flags.                                     |
| `_transaction` | `clientTxId`      | Outbox of pending transactions. Indexes: `byState`, `byCreatedAt`, `byBatchIndex`. |
| `_sync_action` | `id`              | Persisted sync actions for debugging.                                              |
| `<model_hash>` | Model primary key | One store per registered model. Store names are deterministic hashes.              |

### Partial databases

For models with `"partial"` load strategy, a separate database stores partial index entries:

| Store           | Key           | Description                                           |
| --------------- | ------------- | ----------------------------------------------------- |
| `partial_index` | Composite key | Tracks which index key/value pairs have been fetched. |

### Database registry

A top-level `stratasync_databases` database tracks all workspace databases:

| Store       | Key    | Description                                                                    |
| ----------- | ------ | ------------------------------------------------------------------------------ |
| `databases` | `name` | Maps database names to `DatabaseInfo` (userId, version, schemaHash, and more). |

</details>

## Schema migrations

When the schema changes (via `computeSchemaHash`), the adapter increments the IndexedDB version, creates new object stores, adds indexes for `@Property({ indexed: true })` fields, and preserves unchanged stores. This happens transparently during `open()`.

## Multi-workspace support

Deterministic database naming supports multiple users and versions.

```ts
import { computeWorkspaceDatabaseName } from "@stratasync/storage-idb";

const dbName = computeWorkspaceDatabaseName({
  userId: "user-abc",
  version: 1,
  userVersion: 1,
});
// Returns: "ss_<32-char-hash>"
```

Each user/version combination gets its own database, so switching accounts doesn't require clearing data.

## Utility exports

### Store name functions

Deterministic names for stores and databases:

```ts
import {
  computeModelStoreName,
  computeWorkspaceDatabaseName,
  computePartialDatabaseName,
} from "@stratasync/storage-idb";
```

| Function                                                         | Description                                                |
| ---------------------------------------------------------------- | ---------------------------------------------------------- |
| `computeModelStoreName(modelName, schemaVersion, registry)`      | Deterministic hash-based store name for a model.           |
| `computeWorkspaceDatabaseName({ userId, version, userVersion })` | Deterministic database name for a workspace.               |
| `computePartialDatabaseName(storeName)`                          | Derives the partial index database name from a store name. |

### Granular store operations

For testing and advanced use:

```ts
import {
  // Model rows
  getAllModelRows,
  putManyModelRows,
  deleteManyModelRows,
  countModelRows,
  iterateModelRows,

  // Metadata
  getMetadata,
  setMetadata,
  getLastSyncId,
  setLastSyncId,
  isBootstrapComplete,
  setBootstrapComplete,

  // Outbox
  addTransaction,
  getAllTransactions,
  getTransactionsByState,
  markTransactionSent,
  markTransactionCompleted,
  markTransactionFailed,
  removeTransaction,
  clearOutbox,

  // Partial indexes
  getPartialIndex,
  setPartialIndex,
  hasPartialIndex,
  clearPartialIndexes,
} from "@stratasync/storage-idb";
```

Additional granular operations for metadata, outbox, and partial indexes are also available.

## Architecture role

Provides client-side persistence.

```
sync-core (defines Transaction, SyncAction types)
  ^-- sync-storage-idb (implements StorageAdapter with IndexedDB)
        ^-- sync-client (uses adapter for offline persistence)
```

The same interface can be implemented with other backends (SQLite, in-memory).