tauri-plugin-configurate-api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +351 -0
- package/dist-js/index.cjs +497 -0
- package/dist-js/index.d.ts +354 -0
- package/dist-js/index.js +485 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Crysta1221
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# tauri-plugin-configurate
|
|
2
|
+
|
|
3
|
+
A Tauri v2 plugin for type-safe application configuration management.
|
|
4
|
+
|
|
5
|
+
Define your config schema once in TypeScript and get full type inference for reads and writes. Supports JSON, YAML, and encrypted binary formats, with first-class OS keyring integration for storing secrets securely off disk.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Type-safe schema** — define your config shape with `defineConfig()` and get compile-time checked reads/writes
|
|
10
|
+
- **OS keyring support** — mark fields with `keyring()` to store secrets in the native credential store (Keychain / Credential Manager / libsecret) and keep them off disk
|
|
11
|
+
- **Multiple formats** — JSON (human-readable), YAML (human-readable), binary (compact), or encrypted binary (XChaCha20-Poly1305)
|
|
12
|
+
- **Minimal IPC** — every operation (file read + keyring fetch) is batched into a single IPC round-trip
|
|
13
|
+
- **Multiple config files** — use `ConfigurateFactory` to manage multiple files with different schemas from one place
|
|
14
|
+
- **Path traversal protection** — config identifiers and sub-directory paths are validated before use as file names; `/`, `\`, `:`, `*`, `?`, `"`, `<`, `>`, `|`, `.`, `..`, and null bytes are all rejected
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### Rust
|
|
19
|
+
|
|
20
|
+
Add the plugin to `src-tauri/Cargo.toml`:
|
|
21
|
+
|
|
22
|
+
```toml
|
|
23
|
+
[dependencies]
|
|
24
|
+
tauri-plugin-configurate = { path = "/path/to/tauri-plugin-configurate" }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Register it in `src-tauri/src/lib.rs`:
|
|
28
|
+
|
|
29
|
+
```rust
|
|
30
|
+
pub fn run() {
|
|
31
|
+
tauri::Builder::default()
|
|
32
|
+
.plugin(tauri_plugin_configurate::init())
|
|
33
|
+
.run(tauri::generate_context!())
|
|
34
|
+
.expect("error while running tauri application");
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### JavaScript / TypeScript
|
|
39
|
+
|
|
40
|
+
Install the guest bindings:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
# npm
|
|
44
|
+
npm install tauri-plugin-configurate-api
|
|
45
|
+
|
|
46
|
+
# pnpm
|
|
47
|
+
pnpm add tauri-plugin-configurate-api
|
|
48
|
+
|
|
49
|
+
# bun
|
|
50
|
+
bun add tauri-plugin-configurate-api
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Capabilities (permissions)
|
|
54
|
+
|
|
55
|
+
Add the following to your capability file (e.g. `src-tauri/capabilities/default.json`):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"permissions": ["configurate:default"]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`configurate:default` grants access to all plugin commands. You can also allow them individually:
|
|
64
|
+
|
|
65
|
+
| Permission | Description |
|
|
66
|
+
| -------------------------- | ------------------------------------------ |
|
|
67
|
+
| `configurate:allow-create` | Allow creating a new config file |
|
|
68
|
+
| `configurate:allow-load` | Allow loading a config file |
|
|
69
|
+
| `configurate:allow-save` | Allow saving (overwriting) a config file |
|
|
70
|
+
| `configurate:allow-delete` | Allow deleting a config file |
|
|
71
|
+
| `configurate:allow-unlock` | Allow fetching secrets from the OS keyring |
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
### 1. Define a schema
|
|
76
|
+
|
|
77
|
+
Use `defineConfig()` to declare the shape of your config. Primitive fields use constructor values (`String`, `Number`, `Boolean`). Nested objects are supported. Fields that should be stored in the OS keyring are wrapped with `keyring()`.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import {
|
|
81
|
+
defineConfig,
|
|
82
|
+
keyring,
|
|
83
|
+
ConfigurateFactory,
|
|
84
|
+
BaseDirectory,
|
|
85
|
+
} from "tauri-plugin-configurate-api";
|
|
86
|
+
|
|
87
|
+
const appSchema = defineConfig({
|
|
88
|
+
theme: String,
|
|
89
|
+
language: String,
|
|
90
|
+
fontSize: Number,
|
|
91
|
+
notifications: Boolean,
|
|
92
|
+
database: {
|
|
93
|
+
host: String,
|
|
94
|
+
port: Number,
|
|
95
|
+
// stored in the OS keyring — never written to disk
|
|
96
|
+
password: keyring(String, { id: "db-password" }),
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`keyring()` IDs must be unique within a schema. Duplicates are caught at both compile time and runtime.
|
|
102
|
+
|
|
103
|
+
### 2. Create a factory
|
|
104
|
+
|
|
105
|
+
`ConfigurateFactory` holds shared options (`dir`, `format`, optional `subDir`, optional `encryptionKey`) and produces `Configurate` instances — one per config file.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const factory = new ConfigurateFactory({
|
|
109
|
+
dir: BaseDirectory.AppConfig,
|
|
110
|
+
format: "json",
|
|
111
|
+
// Optional: store all files under <AppConfig>/my-app/
|
|
112
|
+
// subDir: "my-app",
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
> **`subDir`** — a forward-slash-separated relative path (e.g. `"my-app"` or `"my-app/config"`) appended between the base directory and the config file name. Each path component must not be empty, `.`, `..`, or contain Windows-forbidden characters. When omitted, files are written directly into `dir`.
|
|
117
|
+
|
|
118
|
+
### 3. Build a `Configurate` instance
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const appConfig = factory.build(appSchema, "app"); // → app.json
|
|
122
|
+
|
|
123
|
+
// Override the factory-level subDir for one specific file:
|
|
124
|
+
const specialConfig = factory.build(specialSchema, "special", "other-dir"); // → <AppConfig>/other-dir/special.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Each call to `build()` can use a different schema, `id`, and/or `subDir`.
|
|
128
|
+
|
|
129
|
+
### 4. Create, load, save, delete
|
|
130
|
+
|
|
131
|
+
All file operations return a `LazyConfigEntry` that you execute with `.run()` or `.unlock()`.
|
|
132
|
+
|
|
133
|
+
#### Create
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await appConfig
|
|
137
|
+
.create({
|
|
138
|
+
theme: "dark",
|
|
139
|
+
language: "en",
|
|
140
|
+
fontSize: 14,
|
|
141
|
+
notifications: true,
|
|
142
|
+
database: { host: "localhost", port: 5432, password: "s3cr3t" },
|
|
143
|
+
})
|
|
144
|
+
.lock({ service: "my-app", account: "default" }) // write password to keyring
|
|
145
|
+
.run();
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### Load (secrets remain `null`)
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const locked = await appConfig.load().run();
|
|
152
|
+
|
|
153
|
+
locked.data.theme; // "dark"
|
|
154
|
+
locked.data.database.password; // null ← secret is not in memory
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Load and unlock in one IPC call
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const unlocked = await appConfig.load().unlock({ service: "my-app", account: "default" });
|
|
161
|
+
|
|
162
|
+
unlocked.data.database.password; // "s3cr3t"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Unlock a `LockedConfig` later (no file re-read)
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const locked = await appConfig.load().run();
|
|
169
|
+
// ... pass locked.data to the UI without secrets ...
|
|
170
|
+
const unlocked = await locked.unlock({ service: "my-app", account: "default" });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`locked.unlock()` issues a single IPC call that reads only from the OS keyring — the file is not read again.
|
|
174
|
+
|
|
175
|
+
#### Save
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
await appConfig
|
|
179
|
+
.save({
|
|
180
|
+
theme: "light",
|
|
181
|
+
language: "ja",
|
|
182
|
+
fontSize: 16,
|
|
183
|
+
notifications: false,
|
|
184
|
+
database: { host: "db.example.com", port: 5432, password: "newpass" },
|
|
185
|
+
})
|
|
186
|
+
.lock({ service: "my-app", account: "default" })
|
|
187
|
+
.run();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Delete
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// Pass keyring options to wipe secrets from the OS keyring as well.
|
|
194
|
+
await appConfig.delete({ service: "my-app", account: "default" });
|
|
195
|
+
|
|
196
|
+
// Omit keyring options when the schema has no keyring fields.
|
|
197
|
+
await appConfig.delete();
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Multiple config files
|
|
203
|
+
|
|
204
|
+
Use `ConfigurateFactory` to manage several config files — each can have a different schema, id, or format.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const appSchema = defineConfig({ theme: String, language: String });
|
|
208
|
+
const cacheSchema = defineConfig({ lastSync: Number });
|
|
209
|
+
const secretSchema = defineConfig({
|
|
210
|
+
token: keyring(String, { id: "api-token" }),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const factory = new ConfigurateFactory({
|
|
214
|
+
dir: BaseDirectory.AppConfig,
|
|
215
|
+
format: "json",
|
|
216
|
+
subDir: "my-app", // all files stored under <AppConfig>/my-app/
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const appConfig = factory.build(appSchema, "app"); // → my-app/app.json
|
|
220
|
+
const cacheConfig = factory.build(cacheSchema, "cache"); // → my-app/cache.json
|
|
221
|
+
const secretConfig = factory.build(secretSchema, "secrets"); // → my-app/secrets.json
|
|
222
|
+
|
|
223
|
+
// Override subDir per-file when needed:
|
|
224
|
+
const legacyConfig = factory.build(legacySchema, "legacy", "old-dir"); // → old-dir/legacy.json
|
|
225
|
+
|
|
226
|
+
// Each instance is a full Configurate — all operations are available
|
|
227
|
+
const app = await appConfig.load().run();
|
|
228
|
+
const cache = await cacheConfig.load().run();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Encrypted binary format
|
|
232
|
+
|
|
233
|
+
Set `format: "binary"` and provide an `encryptionKey` to store config files encrypted with **XChaCha20-Poly1305**. The 32-byte cipher key is derived internally via SHA-256, so any high-entropy string is suitable — a random key stored in the OS keyring is ideal.
|
|
234
|
+
|
|
235
|
+
Encrypted files use the **`.binc`** extension (plain binary files use `.bin`). Never mix backends: opening a `.binc` file with the wrong or missing key returns an error; opening a plain `.bin` file with an `encryptionKey` also returns a decryption error.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
const encKey = await getEncryptionKeyFromKeyring(); // your own retrieval logic
|
|
239
|
+
|
|
240
|
+
const factory = new ConfigurateFactory({
|
|
241
|
+
dir: BaseDirectory.AppConfig,
|
|
242
|
+
format: "binary",
|
|
243
|
+
encryptionKey: encKey,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const config = factory.build(appSchema, "app"); // → app.binc (encrypted)
|
|
247
|
+
|
|
248
|
+
await config.create({ theme: "dark", language: "en" /* ... */ }).run();
|
|
249
|
+
const locked = await config.load().run();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
On-disk format: `[24-byte random nonce][ciphertext + 16-byte Poly1305 tag]`.
|
|
253
|
+
|
|
254
|
+
> **Note** — `encryptionKey` is only valid with `format: "binary"`. Providing it with `"json"` or `"yaml"` throws an error at construction time.
|
|
255
|
+
|
|
256
|
+
## API reference
|
|
257
|
+
|
|
258
|
+
### `defineConfig(schema)`
|
|
259
|
+
|
|
260
|
+
Validates the schema for duplicate keyring IDs and returns it typed as `S`. Throws at runtime if a duplicate ID is found.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
const schema = defineConfig({ name: String, port: Number });
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `keyring(type, { id })`
|
|
267
|
+
|
|
268
|
+
Marks a schema field as keyring-protected. The field is stored in the OS keyring and appears as `null` in the on-disk file and in `LockedConfig.data`.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
keyring(String, { id: "my-secret" });
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### `ConfigurateFactory`
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
new ConfigurateFactory(baseOpts: ConfigurateBaseOptions)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`ConfigurateBaseOptions` is `ConfigurateOptions` without `id`:
|
|
281
|
+
|
|
282
|
+
| Field | Type | Description |
|
|
283
|
+
| --------------- | --------------- | --------------------------------------------------- |
|
|
284
|
+
| `dir` | `BaseDirectory` | Base directory for all files |
|
|
285
|
+
| `subDir` | `string?` | Sub-directory path relative to `dir` (optional) |
|
|
286
|
+
| `format` | `StorageFormat` | `"json"`, `"yaml"`, or `"binary"` |
|
|
287
|
+
| `encryptionKey` | `string?` | Encryption key (binary format only, yields `.binc`) |
|
|
288
|
+
|
|
289
|
+
#### `factory.build(schema, id, subDir?)`
|
|
290
|
+
|
|
291
|
+
Returns a `Configurate<S>` for the given schema and file stem. The optional `subDir` argument overrides the factory-level `subDir` for this specific instance.
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
factory.build(schema, "app") // → <dir>/app.json
|
|
295
|
+
factory.build(schema, "app", "my-app") // → <dir>/my-app/app.json
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### `Configurate<S>`
|
|
299
|
+
|
|
300
|
+
| Method | Returns | Description |
|
|
301
|
+
| ---------------- | -------------------- | ---------------------------------------- |
|
|
302
|
+
| `.create(data)` | `LazyConfigEntry<S>` | Write a new config file |
|
|
303
|
+
| `.load()` | `LazyConfigEntry<S>` | Read an existing config file |
|
|
304
|
+
| `.save(data)` | `LazyConfigEntry<S>` | Overwrite an existing config file |
|
|
305
|
+
| `.delete(opts?)` | `Promise<void>` | Delete the file and wipe keyring entries |
|
|
306
|
+
|
|
307
|
+
### `LazyConfigEntry<S>`
|
|
308
|
+
|
|
309
|
+
| Method | Returns | Description |
|
|
310
|
+
| --------------- | ---------------------------- | ----------------------------------------------------- |
|
|
311
|
+
| `.lock(opts)` | `this` | Attach keyring options (chainable, before run/unlock) |
|
|
312
|
+
| `.run()` | `Promise<LockedConfig<S>>` | Execute — secrets are `null` |
|
|
313
|
+
| `.unlock(opts)` | `Promise<UnlockedConfig<S>>` | Execute — secrets are inlined (single IPC call) |
|
|
314
|
+
|
|
315
|
+
### `LockedConfig<S>`
|
|
316
|
+
|
|
317
|
+
| Member | Type | Description |
|
|
318
|
+
| --------------- | ---------------------------- | ----------------------------------------- |
|
|
319
|
+
| `.data` | `InferLocked<S>` | Config data with keyring fields as `null` |
|
|
320
|
+
| `.unlock(opts)` | `Promise<UnlockedConfig<S>>` | Fetch secrets without re-reading the file |
|
|
321
|
+
|
|
322
|
+
### `UnlockedConfig<S>`
|
|
323
|
+
|
|
324
|
+
| Member | Type | Description |
|
|
325
|
+
| --------- | ------------------ | ------------------------------------ |
|
|
326
|
+
| `.data` | `InferUnlocked<S>` | Config data with all secrets inlined |
|
|
327
|
+
| `.lock()` | `void` | Drop in-memory secrets (GC-assisted) |
|
|
328
|
+
|
|
329
|
+
## IPC call count
|
|
330
|
+
|
|
331
|
+
| Operation | IPC calls |
|
|
332
|
+
| ------------------------------------------- | --------- |
|
|
333
|
+
| `create` / `save` (with or without keyring) | 1 |
|
|
334
|
+
| `load` (no keyring) | 1 |
|
|
335
|
+
| `load().unlock(opts)` | 1 |
|
|
336
|
+
| `load().run()` then `locked.unlock(opts)` | 2 |
|
|
337
|
+
| `delete` | 1 |
|
|
338
|
+
|
|
339
|
+
## Security considerations
|
|
340
|
+
|
|
341
|
+
- **Secrets off disk** — keyring fields are set to `null` before the file is written; the plaintext never touches the filesystem.
|
|
342
|
+
- **Path traversal protection** — config IDs and `subDir` components containing `/`, `\`, `:`, `*`, `?`, `"`, `<`, `>`, `|`, bare `.` or `..`, and null bytes are rejected with an `invalid payload` error.
|
|
343
|
+
- **Encrypted binary (`.binc`)** — XChaCha20-Poly1305 provides authenticated encryption; any tampering with the ciphertext is detected at read time and returns an error. Encrypted files are distinguished from plain binary (`.bin`) by their extension.
|
|
344
|
+
- **Binary ≠ encrypted** — `format: "binary"` without `encryptionKey` stores data as plain bincode-encoded JSON (`.bin`). Use `encryptionKey` when confidentiality is required.
|
|
345
|
+
- **Key entropy** — when using `encryptionKey`, provide a high-entropy value (≥ 128 bits of randomness). A randomly generated key stored in the OS keyring is recommended.
|
|
346
|
+
- **Keyring availability** — the OS keyring may not be available in all environments (e.g. headless CI). Handle `keyring error` responses gracefully in those cases.
|
|
347
|
+
- **In-memory secrets** — `UnlockedConfig.data` holds plaintext values in the JS heap until GC collection. JavaScript provides no guaranteed way to zero-out memory, so avoid keeping `UnlockedConfig` objects alive longer than necessary.
|
|
348
|
+
|
|
349
|
+
## License
|
|
350
|
+
|
|
351
|
+
MIT
|