tauri-plugin-configurate 0.3.1

Tauri v2 plugin for type-safe application configuration management.
Documentation
# tauri-plugin-configurate

**Tauri v2 plugin for type-safe application configuration management with OS keyring support.**

Store app settings as JSON, YAML, Binary, or SQLite — with sensitive values automatically secured in the OS keyring (Windows Credential Manager / macOS Keychain / Linux Secret Service).

---

> [!WARNING]
> **Pre-release software (0.x)**
>
> This plugin is under active development and has **not reached a stable 1.0 release**.
>
> - **Breaking changes may be introduced in any minor version** (e.g. 0.2 → 0.3).
> - **Bugs may be present.** Please report issues on [GitHub]https://github.com/Crysta1221/tauri-plugin-configurate/issues.
> - The on-disk format of **`BinaryProvider()` (unencrypted)** changed in v0.2.3 — existing files written by v0.2.2 or earlier must be re-created. Encrypted binary files (`BinaryProvider({ encryptionKey })`) are not affected.
>
> Pin to an exact version in production and review the [release notes]https://github.com/Crysta1221/tauri-plugin-configurate/releases before upgrading.

---

## Features

| Feature                       | Description                                                                                      |
| ----------------------------- | ------------------------------------------------------------------------------------------------ |
| 🛡️ **Type-safe schema**       | Define your config shape with `defineConfig()` — TypeScript infers all value types automatically |
| 🔑 **OS keyring integration** | Mark sensitive fields with `keyring()` — secrets never touch disk                                |
| 🧩 **Multiple providers**     | Choose JSON, YAML, Binary (encrypted or plain), or SQLite as the storage backend                 |
| 📄 **Single-file API**        | `create` / `load` / `save` / `delete` / `unlock` — consistent builder-style calls                |
| 📦 **Batch API**              | `loadAll` / `saveAll` — load or save multiple configs in a single IPC round-trip                 |
| 🗂️ **Flexible paths**         | Control the storage location with `baseDir`, `options.dirName`, and `options.currentPath`        |

---

## Installation

### 1. Add the Rust plugin

```toml
# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-configurate = "0.x.x"
```

Register it in `src-tauri/src/lib.rs`:

```rust
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_configurate::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

**or, use tauri cli.**

```sh
tauri add configurate
```

### 2. Add the JavaScript / TypeScript API

```sh
# npm
npm install tauri-plugin-configurate-api

# pnpm
pnpm add tauri-plugin-configurate-api

# yarn
yarn add tauri-plugin-configurate-api

# bun
bun add tauri-plugin-configurate-api
```

> **Tip:** If you use the Tauri CLI, `tauri add configurate` handles both steps automatically.

### 3. Grant permissions

Add the following to your capability file (e.g. `src-tauri/capabilities/default.json`):

```json
{
  "permissions": ["configurate:default"]
}
```

`configurate:default` expands to:

| Permission                   | Operation                  |
| ---------------------------- | -------------------------- |
| `configurate:allow-create`   | Create a new config file   |
| `configurate:allow-load`     | Read a config file         |
| `configurate:allow-save`     | Write/update a config file |
| `configurate:allow-delete`   | Delete a config file       |
| `configurate:allow-load-all` | Batch load                 |
| `configurate:allow-save-all` | Batch save                 |
| `configurate:allow-unlock`   | Inline keyring decryption  |

---

## Quick Start

### Step 1 — Define your schema

```ts
import { defineConfig, keyring } from "tauri-plugin-configurate-api";

const appSchema = defineConfig({
  theme: String,
  language: String,
  database: {
    host: String,
    // "password" is stored in the OS keyring, never written to disk
    password: keyring(String, { id: "db-password" }),
  },
});
```

`defineConfig()` validates at runtime that all `keyring()` IDs are unique within the schema.

### Step 2 — Create a `Configurate` instance

```ts
import { BaseDirectory, Configurate, JsonProvider } from "tauri-plugin-configurate-api";

const config = new Configurate({
  schema: appSchema,
  fileName: "app.json", // filename only, no path separators
  baseDir: BaseDirectory.AppConfig,
  provider: JsonProvider(),
  options: {
    dirName: "my-app", // replaces the app identifier segment
    currentPath: "config/v2", // sub-directory within the root
  },
});
```

The resolved path is: `{AppConfig}/my-app/config/v2/app.json`

### Step 3 — Read and write

```ts
const KEYRING = { service: "my-app", account: "default" };

// Create — writes plain fields to disk, stores secrets in the OS keyring
await config
  .create({
    theme: "dark",
    language: "ja",
    database: { host: "localhost", password: "secret" },
  })
  .lock(KEYRING) // KEYRING opts are required when the schema has keyring fields
  .run();

// Load (locked) — keyring fields come back as null
const locked = await config.load().run();
console.log(locked.data.database.password); // null

// Load (unlocked) — keyring fields are filled from the OS keyring
const unlocked = await config.load().unlock(KEYRING);
console.log(unlocked.data.database.password); // "secret"

// Save — same pattern as create
await config
  .save({
    theme: "light",
    language: "en",
    database: { host: "db.example.com", password: "next-secret" },
  })
  .lock(KEYRING)
  .run();

// Delete — removes the file and wipes keyring entries
await config.delete(KEYRING);
```

---

## Providers

Choose the storage format when constructing a `Configurate` instance.

```ts
import {
  JsonProvider,
  YmlProvider,
  BinaryProvider,
  SqliteProvider,
} from "tauri-plugin-configurate-api";

// Plain JSON (human-readable)
JsonProvider();

// YAML
YmlProvider();

// Encrypted binary using XChaCha20-Poly1305
// The key is hashed via SHA-256 internally — use a high-entropy string
BinaryProvider({ encryptionKey: "high-entropy-key" });

// Unencrypted binary (compact JSON bytes, no human-readable format)
BinaryProvider();

// SQLite — all schema fields become typed columns
SqliteProvider({ dbName: "app.db", tableName: "configs" });
```

> [!NOTE]
> `BinaryProvider()` without an `encryptionKey` provides **no confidentiality**.
> Use `BinaryProvider({ encryptionKey })` or the OS keyring for sensitive values.

---

## Batch Operations

Load or save multiple configs in a **single IPC call** with `loadAll` / `saveAll`.

```ts
const appConfig = new Configurate({
  schema: defineConfig({ theme: String }),
  fileName: "app.json",
  baseDir: BaseDirectory.AppConfig,
  provider: JsonProvider(),
});

const secretConfig = new Configurate({
  schema: defineConfig({ token: keyring(String, { id: "api-token" }) }),
  fileName: "secret.bin",
  baseDir: BaseDirectory.AppConfig,
  provider: BinaryProvider({ encryptionKey: "high-entropy-key" }),
});

// Load all — unlock a specific entry by id
const loaded = await Configurate.loadAll([
  { id: "app", config: appConfig },
  { id: "secret", config: secretConfig },
])
  .unlock("secret", { service: "my-app", account: "default" })
  .run();

// Save all — lock a specific entry by id
const saved = await Configurate.saveAll([
  { id: "app", config: appConfig, data: { theme: "dark" } },
  { id: "secret", config: secretConfig, data: { token: "next-token" } },
])
  .lock("secret", { service: "my-app", account: "default" })
  .run();
```

### Result shape

Each entry in `results` is either a success or a per-entry failure — a single entry failing **does not abort the batch**.

```ts
type BatchRunResult = {
  results: {
    [id: string]:
      | { ok: true; data: unknown }
      | { ok: false; error: { kind: string; message: string } };
  };
};

// Access individual results
loaded.results.app; // { ok: true, data: { theme: "dark" } }
loaded.results.secret; // { ok: true, data: { token: "..." } }
```

---

## Path Resolution

| Option                | Effect                                                                                                                   |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `baseDir`             | Tauri `BaseDirectory` enum value (e.g. `AppConfig`, `AppData`, `Desktop`)                                                |
| `options.dirName`     | Replaces the app-identifier segment when it is the last segment of `baseDir` path; otherwise appended as a sub-directory |
| `options.currentPath` | Sub-directory appended after the `dirName` root                                                                          |
| `fileName`            | Single filename — **must not contain path separators**                                                                   |

Example — `baseDir: AppConfig`, `dirName: "my-app"`, `currentPath: "v2"`, `fileName: "settings.json"`:

```
Windows:  C:\Users\<user>\AppData\Roaming\my-app\v2\settings.json
macOS:    ~/Library/Application Support/my-app/v2/settings.json
Linux:    ~/.config/my-app/v2/settings.json
```

---

## Compatibility

The following **deprecated** forms are still accepted in the current minor version and automatically normalized to the new API. Each emits one `console.warn` per process.

| Deprecated form                 | Replacement                                                      |
| ------------------------------- | ---------------------------------------------------------------- |
| `new Configurate(schema, opts)` | `new Configurate({ schema, ...opts })`                           |
| `ConfigurateFactory`            | `new Configurate({ ... })`                                       |
| `dir`                           | `baseDir`                                                        |
| `name`                          | `fileName`                                                       |
| `path`                          | `options.currentPath`                                            |
| `format` + `encryptionKey`      | `provider: JsonProvider()` / `BinaryProvider({ encryptionKey })` |

> These compatibility shims will be **removed in the next minor release**.

---

## License

MIT © [Crysta1221](https://github.com/Crysta1221)