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.
@@ -0,0 +1,354 @@
1
+ export { BaseDirectory } from "@tauri-apps/api/path";
2
+ import type { BaseDirectory } from "@tauri-apps/api/path";
3
+ /** Supported on-disk storage formats **/
4
+ export type StorageFormat = "json" | "yaml" | "binary";
5
+ /** Phantom symbol used only in the type system – never appears at runtime. */
6
+ declare const _keyringBrandTag: unique symbol;
7
+ /**
8
+ * Marker type produced by `keyring()`.
9
+ * `T` – the runtime type of the secret value.
10
+ * `Id` – the literal string id used to key the OS keyring entry.
11
+ */
12
+ export type KeyringField<T, Id extends string> = {
13
+ readonly [_keyringBrandTag]?: true;
14
+ readonly _type: T;
15
+ readonly _id: Id;
16
+ };
17
+ /** Options required when creating a keyring-protected field definition. */
18
+ export interface KeyringFieldOptions<Id extends string> {
19
+ /** Unique identifier for this keyring entry. Must be unique within a schema. */
20
+ id: Id;
21
+ }
22
+ /**
23
+ * Marks a schema field as keyring-protected.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const schema = defineConfig({
28
+ * apiKey: keyring(String, { id: "api-key" }),
29
+ * });
30
+ * ```
31
+ */
32
+ export declare function keyring<T, Id extends string>(_type: abstract new (...args: never[]) => T | ((...args: never[]) => T), opts: KeyringFieldOptions<Id>): KeyringField<T, Id>;
33
+ /** Primitive constructor types supported in a schema definition. */
34
+ type PrimitiveConstructor = StringConstructor | NumberConstructor | BooleanConstructor;
35
+ /** Maps a primitive constructor to its corresponding TS type. */
36
+ type InferPrimitive<C> = C extends StringConstructor ? string : C extends NumberConstructor ? number : C extends BooleanConstructor ? boolean : never;
37
+ /** Any value that is valid inside a schema definition. */
38
+ export type SchemaValue = PrimitiveConstructor | KeyringField<unknown, string> | SchemaObject;
39
+ /** A plain object whose values are all `SchemaValue`s. */
40
+ export type SchemaObject = {
41
+ [key: string]: SchemaValue;
42
+ };
43
+ /**
44
+ * Recursively collects every keyring id found anywhere inside a schema as a
45
+ * union of string literals.
46
+ */
47
+ export type CollectKeyringIds<S extends SchemaObject> = {
48
+ [K in keyof S]: S[K] extends KeyringField<unknown, infer Id> ? Id : S[K] extends SchemaObject ? CollectKeyringIds<S[K]> : never;
49
+ }[keyof S];
50
+ /**
51
+ * Produces `never` when a string literal `T` appears more than once inside
52
+ * the union `All`. Used to detect duplicate keyring ids at compile time.
53
+ *
54
+ * The check works by removing `T` from `All` and seeing whether there are
55
+ * still members left that match `T`. If after exclusion some member equals
56
+ * `T`, it means the original union contained `T` at least twice.
57
+ */
58
+ type IsDuplicate<T extends string, All extends string> = T extends Exclude<All, T> ? true : false;
59
+ /**
60
+ * Evaluates to `true` if any keyring id appears more than once in the
61
+ * schema, `false` otherwise.
62
+ */
63
+ export type HasDuplicateKeyringIds<S extends SchemaObject> = true extends {
64
+ [Id in CollectKeyringIds<S>]: IsDuplicate<Id, CollectKeyringIds<S>>;
65
+ }[CollectKeyringIds<S>] ? true : false;
66
+ /**
67
+ * Converts a schema into the "locked" config type where every
68
+ * `KeyringField<T, Id>` becomes `null`.
69
+ */
70
+ export type InferLocked<S extends SchemaObject> = {
71
+ [K in keyof S]: S[K] extends KeyringField<unknown, string> ? null : S[K] extends SchemaObject ? InferLocked<S[K]> : S[K] extends PrimitiveConstructor ? InferPrimitive<S[K]> : never;
72
+ };
73
+ /**
74
+ * Converts a schema into the "unlocked" config type where every
75
+ * `KeyringField<T, Id>` is replaced with the actual secret type `T`.
76
+ */
77
+ export type InferUnlocked<S extends SchemaObject> = {
78
+ [K in keyof S]: S[K] extends KeyringField<infer T, string> ? T : S[K] extends SchemaObject ? InferUnlocked<S[K]> : S[K] extends PrimitiveConstructor ? InferPrimitive<S[K]> : never;
79
+ };
80
+ /**
81
+ * Options required to access the OS keyring.
82
+ * Stored with OS keyring fields:
83
+ * - service = `{service}`
84
+ * - user = `{account}/{id}`
85
+ */
86
+ export interface KeyringOptions {
87
+ /** The keyring service name (e.g. your application name). */
88
+ service: string;
89
+ /** The keyring account name (e.g. `"default"`). */
90
+ account: string;
91
+ }
92
+ /**
93
+ * A loaded configuration where keyring-protected fields are `null`.
94
+ * Call `.unlock(opts)` to fetch secrets and obtain an `UnlockedConfig<S>`.
95
+ *
96
+ * `unlock()` issues a single IPC call that only reads from the OS keyring —
97
+ * it does **not** re-read the file from disk.
98
+ */
99
+ export declare class LockedConfig<S extends SchemaObject> {
100
+ private readonly _configurate;
101
+ readonly data: InferLocked<S>;
102
+ /** @internal */
103
+ constructor(data: InferLocked<S>, _configurate: Configurate<S>);
104
+ /**
105
+ * Fetches all keyring secrets and returns an `UnlockedConfig`.
106
+ * Issues a single IPC call (keyring read only – file is not re-read).
107
+ */
108
+ unlock(opts: KeyringOptions): Promise<UnlockedConfig<S>>;
109
+ }
110
+ /**
111
+ * A configuration where all keyring-protected fields contain their real values.
112
+ * Call `.lock()` to discard in-memory secrets (no IPC required).
113
+ */
114
+ export declare class UnlockedConfig<S extends SchemaObject> {
115
+ private _data;
116
+ /** @internal */
117
+ constructor(data: InferUnlocked<S>);
118
+ /**
119
+ * Returns the unlocked configuration data.
120
+ * Throws if `lock()` has already been called.
121
+ */
122
+ get data(): InferUnlocked<S>;
123
+ /**
124
+ * Discards in-memory secrets. Callers should drop all references to this
125
+ * object after calling `lock()`.
126
+ *
127
+ * > **Security note** — JavaScript does not provide a guaranteed way to
128
+ * > zero-out memory. Calling `lock()` nullifies the top-level reference
129
+ * > but the secret values remain in the JS heap until the GC collects them.
130
+ * > Avoid long-lived `UnlockedConfig` objects when handling sensitive data.
131
+ */
132
+ lock(): void;
133
+ }
134
+ /**
135
+ * A lazy handle returned by `Configurate.load()`, `.create()` and `.save()`.
136
+ *
137
+ * - `await entry.run()` → `LockedConfig<S>` (one IPC, secrets are null)
138
+ * - `await entry.unlock(opts)` → `UnlockedConfig<S>` (one IPC, secrets inlined)
139
+ * - `.lock(opts)` (before awaiting) → write secrets to keyring in the same IPC
140
+ *
141
+ * Use `await entry.run()` instead of `await entry` directly to avoid the
142
+ * `no-thenable` lint rule and unintended Promise behaviour.
143
+ */
144
+ export declare class LazyConfigEntry<S extends SchemaObject> {
145
+ private readonly _configurate;
146
+ private readonly _op;
147
+ private readonly _data?;
148
+ private _keyringOpts;
149
+ /** @internal */
150
+ constructor(_configurate: Configurate<S>, _op: "create" | "load" | "save", _data?: InferUnlocked<S> | undefined);
151
+ /**
152
+ * Attaches keyring options so secrets are written to / read from the OS
153
+ * keyring in the same IPC call as the main operation.
154
+ *
155
+ * Returns `this` to allow chaining: `entry.lock(opts).run()`.
156
+ */
157
+ lock(opts: KeyringOptions): this;
158
+ /**
159
+ * Executes the operation and returns a `LockedConfig` (secrets are null).
160
+ * Issues a single IPC call.
161
+ */
162
+ run(): Promise<LockedConfig<S>>;
163
+ /**
164
+ * Executes the operation and returns an `UnlockedConfig` (secrets inlined).
165
+ * Issues a single IPC call – no extra round-trip compared to `run()`.
166
+ */
167
+ unlock(opts: KeyringOptions): Promise<UnlockedConfig<S>>;
168
+ }
169
+ /** Options passed to the `Configurate` constructor. */
170
+ export interface ConfigurateOptions {
171
+ /** Unique identifier for the configuration file (used as the file stem). */
172
+ id: string;
173
+ /** Base directory in which the configuration file will be stored. */
174
+ dir: BaseDirectory;
175
+ /**
176
+ * Optional sub-directory path relative to `dir`.
177
+ *
178
+ * Use forward slashes as separators (e.g. `"my-app/config"`).
179
+ * Each segment is validated on the Rust side; `..` and Windows-forbidden
180
+ * characters are rejected.
181
+ *
182
+ * When omitted, files are written directly into `dir`.
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * // Stores config at <AppConfig>/my-app/settings.json
187
+ * new Configurate(schema, {
188
+ * id: "settings",
189
+ * dir: BaseDirectory.AppConfig,
190
+ * format: "json",
191
+ * subDir: "my-app",
192
+ * });
193
+ * ```
194
+ */
195
+ subDir?: string;
196
+ /** On-disk storage format. */
197
+ format: StorageFormat;
198
+ /**
199
+ * Encryption key for the `"binary"` format.
200
+ *
201
+ * When provided, the file is encrypted with **XChaCha20-Poly1305**. The
202
+ * 32-byte cipher key is derived internally via `SHA-256(encryptionKey)`, so
203
+ * the value should be high-entropy — for example a random key stored in the
204
+ * OS keyring. Omit this field when using `"json"` or `"yaml"` formats, or
205
+ * when backward-compatible unencrypted binary files are required.
206
+ *
207
+ * Encrypted binary files use the `.binc` extension instead of `.bin`.
208
+ */
209
+ encryptionKey?: string;
210
+ }
211
+ /**
212
+ * Main entry point for managing application configuration.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * import { Configurate, defineConfig, keyring, BaseDirectory } from "tauri-plugin-configurate-api";
217
+ *
218
+ * const schema = defineConfig({
219
+ * appName: String,
220
+ * port: Number,
221
+ * apiKey: keyring(String, { id: "api-key" }),
222
+ * });
223
+ *
224
+ * const config = new Configurate(schema, {
225
+ * id: "app-config",
226
+ * dir: BaseDirectory.AppConfig,
227
+ * format: "json",
228
+ * });
229
+ *
230
+ * // Create – IPC ×1
231
+ * await config
232
+ * .create({ appName: "MyApp", port: 3000, apiKey: "secret" })
233
+ * .lock({ service: "my-app", account: "default" })
234
+ * .run();
235
+ *
236
+ * // Load locked – IPC ×1
237
+ * const locked = await config.load().run();
238
+ * locked.data.apiKey; // null
239
+ *
240
+ * // Unlock from locked – IPC ×1 (keyring only, file is not re-read)
241
+ * const unlocked = await locked.unlock({ service: "my-app", account: "default" });
242
+ * unlocked.data.apiKey; // "secret"
243
+ *
244
+ * // Load and unlock in one shot – IPC ×1
245
+ * const unlocked2 = await config.load().unlock({ service: "my-app", account: "default" });
246
+ * ```
247
+ */
248
+ export declare class Configurate<S extends SchemaObject> {
249
+ private readonly _schema;
250
+ private readonly _opts;
251
+ private readonly _keyringPaths;
252
+ constructor(schema: S & (true extends HasDuplicateKeyringIds<S> ? never : unknown), opts: ConfigurateOptions);
253
+ /** Returns a lazy entry that creates the config file on the Rust side. */
254
+ create(data: InferUnlocked<S>): LazyConfigEntry<S>;
255
+ /** Returns a lazy entry that loads the config file from the Rust side. */
256
+ load(): LazyConfigEntry<S>;
257
+ /** Returns a lazy entry that overwrites the config file on the Rust side. */
258
+ save(data: InferUnlocked<S>): LazyConfigEntry<S>;
259
+ /**
260
+ * Deletes the configuration file from disk **and** removes all associated
261
+ * keyring entries from the OS keyring in a single IPC call.
262
+ *
263
+ * Pass `opts` when the schema contains `keyring()` fields so the plugin
264
+ * knows which keyring entries to wipe. Omit `opts` (or pass `null`) when
265
+ * the schema has no keyring fields.
266
+ *
267
+ * Returns `Promise<void>`. Resolves even if the file did not exist.
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * // Schema with keyring fields – pass keyring opts to wipe secrets too.
272
+ * await config.delete({ service: "my-app", account: "default" });
273
+ *
274
+ * // Schema with no keyring fields – opts may be omitted.
275
+ * await config.delete();
276
+ * ```
277
+ */
278
+ delete(opts?: KeyringOptions | null): Promise<void>;
279
+ /** @internal */
280
+ _executeLocked(op: "create" | "load" | "save", data: InferUnlocked<S> | undefined, keyringOpts: KeyringOptions | null): Promise<LockedConfig<S>>;
281
+ /** @internal */
282
+ _executeUnlock(op: "create" | "load" | "save", data: InferUnlocked<S> | undefined, keyringOpts: KeyringOptions): Promise<UnlockedConfig<S>>;
283
+ /**
284
+ * Fetches keyring secrets and merges them into already-loaded plain data
285
+ * without re-reading the file from disk.
286
+ * Issues a single IPC call to `plugin:configurate|unlock`.
287
+ *
288
+ * @internal
289
+ */
290
+ _unlockFromData(plainData: Record<string, unknown>, opts: KeyringOptions): Promise<UnlockedConfig<S>>;
291
+ /** @internal */
292
+ _buildPayload(op: "create" | "load" | "save", data: InferUnlocked<S> | undefined, keyringOpts: KeyringOptions | null, withUnlock: boolean): Record<string, unknown>;
293
+ }
294
+ /**
295
+ * Base options shared across all configs created by a `ConfigurateFactory`.
296
+ * `id` is omitted because each config provides its own.
297
+ */
298
+ export type ConfigurateBaseOptions = Omit<ConfigurateOptions, "id">;
299
+ /**
300
+ * A factory that creates `Configurate` instances with pre-set shared options
301
+ * (`dir`, `format`, and optionally `encryptionKey`).
302
+ *
303
+ * Each call to `build()` creates a fresh `Configurate` instance — schema,
304
+ * `id`, and all other options can differ freely. This is the recommended
305
+ * way to manage multiple config files with different schemas in a single
306
+ * application.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * import { ConfigurateFactory, defineConfig, BaseDirectory } from "tauri-plugin-configurate-api";
311
+ *
312
+ * const appSchema = defineConfig({ theme: String, language: String });
313
+ * const cacheSchema = defineConfig({ lastSync: Number });
314
+ * const secretSchema = defineConfig({ token: String });
315
+ *
316
+ * const factory = new ConfigurateFactory({
317
+ * dir: BaseDirectory.AppConfig,
318
+ * format: "json",
319
+ * });
320
+ *
321
+ * const appConfig = factory.build(appSchema, "app"); // → app.json
322
+ * const cacheConfig = factory.build(cacheSchema, "cache"); // → cache.json
323
+ * const secretConfig = factory.build(secretSchema, "secrets"); // → secrets.json
324
+ * ```
325
+ */
326
+ export declare class ConfigurateFactory {
327
+ private readonly _baseOpts;
328
+ constructor(_baseOpts: ConfigurateBaseOptions);
329
+ /**
330
+ * Creates a `Configurate<S>` for the given schema and id, applying the
331
+ * shared base options.
332
+ *
333
+ * The optional `subDir` argument overrides the factory-level `subDir` for
334
+ * this specific instance.
335
+ */
336
+ build<S extends SchemaObject>(schema: S & (true extends HasDuplicateKeyringIds<S> ? never : unknown), id: string, subDir?: string): Configurate<S>;
337
+ }
338
+ /**
339
+ * Defines a configuration schema. Provides a convenient declaration site and
340
+ * compile-time duplicate keyring id checks.
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * const schema = defineConfig({
345
+ * appName: String,
346
+ * port: Number,
347
+ * database: {
348
+ * host: String,
349
+ * password: keyring(String, { id: "db-password" }),
350
+ * },
351
+ * });
352
+ * ```
353
+ */
354
+ export declare function defineConfig<S extends SchemaObject>(schema: S & (true extends HasDuplicateKeyringIds<S> ? never : unknown)): S;