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
|
@@ -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;
|