@crustjs/store 0.0.2 → 0.0.4
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/README.md +318 -126
- package/dist/index.d.ts +344 -186
- package/dist/index.js +3 -1
- package/package.json +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,169 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Contextual details attached to a `PATH` error.
|
|
3
|
-
*
|
|
4
|
-
* Returned when the resolved or provided config file path is invalid.
|
|
5
|
-
*/
|
|
6
|
-
interface PathErrorDetails {
|
|
7
|
-
/** The path value that caused the error. */
|
|
8
|
-
path: string;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Contextual details attached to a `PARSE` error.
|
|
12
|
-
*
|
|
13
|
-
* Returned when the persisted JSON file cannot be parsed.
|
|
14
|
-
*/
|
|
15
|
-
interface ParseErrorDetails {
|
|
16
|
-
/** Absolute path to the file that failed parsing. */
|
|
17
|
-
path: string;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Contextual details attached to an `IO` error.
|
|
21
|
-
*
|
|
22
|
-
* Returned on filesystem read, write, or delete failures.
|
|
23
|
-
*/
|
|
24
|
-
interface IOErrorDetails {
|
|
25
|
-
/** Absolute path to the file involved in the failed operation. */
|
|
26
|
-
path: string;
|
|
27
|
-
/** The filesystem operation that failed (e.g. `"read"`, `"write"`, `"delete"`). */
|
|
28
|
-
operation: "read" | "write" | "delete";
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Maps each {@link StoreErrorCode} to its structured details type.
|
|
32
|
-
*
|
|
33
|
-
* Used internally for conditional constructor parameters and type narrowing.
|
|
34
|
-
*/
|
|
35
|
-
interface StoreErrorDetailsMap {
|
|
36
|
-
PATH: PathErrorDetails;
|
|
37
|
-
PARSE: ParseErrorDetails;
|
|
38
|
-
IO: IOErrorDetails;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* All possible error codes emitted by `@crustjs/store`.
|
|
42
|
-
*
|
|
43
|
-
* - `PATH` — Invalid or unsupported config file path
|
|
44
|
-
* - `PARSE` — Malformed JSON in persisted config file
|
|
45
|
-
* - `IO` — Filesystem read, write, or delete failure
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```ts
|
|
49
|
-
* import { CrustStoreError } from "@crustjs/store";
|
|
50
|
-
*
|
|
51
|
-
* try {
|
|
52
|
-
* const config = await store.read();
|
|
53
|
-
* } catch (err) {
|
|
54
|
-
* if (err instanceof CrustStoreError) {
|
|
55
|
-
* switch (err.code) {
|
|
56
|
-
* case "PARSE":
|
|
57
|
-
* console.error(`Corrupt config at ${err.details.path}`);
|
|
58
|
-
* break;
|
|
59
|
-
* case "IO":
|
|
60
|
-
* console.error(`File ${err.details.operation} failed: ${err.message}`);
|
|
61
|
-
* break;
|
|
62
|
-
* }
|
|
63
|
-
* }
|
|
64
|
-
* }
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
type StoreErrorCode = keyof StoreErrorDetailsMap;
|
|
68
|
-
/** Resolves the details type for a given {@link StoreErrorCode}. */
|
|
69
|
-
type StoreErrorDetails<C extends StoreErrorCode> = StoreErrorDetailsMap[C];
|
|
70
|
-
/**
|
|
71
|
-
* A typed error thrown by `@crustjs/store` for path, parse, and IO failures.
|
|
72
|
-
*
|
|
73
|
-
* Every `CrustStoreError` carries a {@link StoreErrorCode} that identifies the failure
|
|
74
|
-
* category, along with structured {@link StoreErrorDetails} for programmatic handling
|
|
75
|
-
* without fragile message parsing.
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* ```ts
|
|
79
|
-
* import { CrustStoreError } from "@crustjs/store";
|
|
80
|
-
*
|
|
81
|
-
* try {
|
|
82
|
-
* const config = await store.read();
|
|
83
|
-
* } catch (err) {
|
|
84
|
-
* if (err instanceof CrustStoreError && err.is("PARSE")) {
|
|
85
|
-
* console.error(`Corrupt config at ${err.details.path}`);
|
|
86
|
-
* }
|
|
87
|
-
* }
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
declare class CrustStoreError<C extends StoreErrorCode = StoreErrorCode> extends Error {
|
|
91
|
-
/** Machine-readable error code for programmatic handling. */
|
|
92
|
-
readonly code: C;
|
|
93
|
-
/** Structured payload with context about the failure. */
|
|
94
|
-
readonly details: StoreErrorDetails<C>;
|
|
95
|
-
/** Optional wrapped original error or value. */
|
|
96
|
-
cause?: unknown;
|
|
97
|
-
constructor(code: C, message: string, details: StoreErrorDetails<C>);
|
|
98
|
-
/**
|
|
99
|
-
* Type-guard that narrows this error to a specific {@link StoreErrorCode}.
|
|
100
|
-
*
|
|
101
|
-
* When the guard returns `true`, TypeScript narrows the `details` property
|
|
102
|
-
* to the corresponding details type.
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```ts
|
|
106
|
-
* if (err.is("IO")) {
|
|
107
|
-
* // err.details is IOErrorDetails
|
|
108
|
-
* console.error(err.details.operation, err.details.path);
|
|
109
|
-
* }
|
|
110
|
-
* ```
|
|
111
|
-
*/
|
|
112
|
-
is<T extends StoreErrorCode>(code: T): this is CrustStoreError<T>;
|
|
113
|
-
/**
|
|
114
|
-
* Attaches a cause to this error for stack-trace chaining.
|
|
115
|
-
*
|
|
116
|
-
* Returns `this` for fluent usage.
|
|
117
|
-
*
|
|
118
|
-
* @example
|
|
119
|
-
* ```ts
|
|
120
|
-
* throw new CrustStoreError("IO", "write failed", {
|
|
121
|
-
* path: "/path/to/config.json",
|
|
122
|
-
* operation: "write",
|
|
123
|
-
* }).withCause(originalError);
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
withCause(cause: unknown): this;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Encapsulates runtime environment values needed for path resolution.
|
|
130
|
-
*
|
|
131
|
-
* Exposed as a parameter to allow deterministic testing without mutating
|
|
132
|
-
* `process.env` or `process.platform`.
|
|
133
|
-
*/
|
|
134
|
-
interface PlatformEnv {
|
|
135
|
-
/** Operating system platform identifier (e.g. `"linux"`, `"darwin"`, `"win32"`). */
|
|
136
|
-
platform: string;
|
|
137
|
-
/** Environment variables map. */
|
|
138
|
-
env: Record<string, string | undefined>;
|
|
139
|
-
/** User home directory path. */
|
|
140
|
-
homedir: string;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Resolves the platform-standard config directory for the given app.
|
|
144
|
-
*
|
|
145
|
-
* Platform conventions:
|
|
146
|
-
* - **Linux**: `$XDG_CONFIG_HOME/<appName>` or `~/.config/<appName>`
|
|
147
|
-
* - **macOS**: `~/Library/Application Support/<appName>`
|
|
148
|
-
* - **Windows**: `%APPDATA%/<appName>` or `~/AppData/Roaming/<appName>`
|
|
149
|
-
*
|
|
150
|
-
* @param appName - Application name used as directory name. Must be a non-empty
|
|
151
|
-
* string without path separators.
|
|
152
|
-
* @param env - Optional platform environment override for testing.
|
|
153
|
-
* @returns Absolute path to the app's config directory.
|
|
154
|
-
* @throws {CrustStoreError} `PATH` if `appName` is invalid or platform is unsupported.
|
|
155
|
-
*
|
|
156
|
-
* @example
|
|
157
|
-
* ```ts
|
|
158
|
-
* import { configDir } from "@crustjs/store";
|
|
159
|
-
*
|
|
160
|
-
* const dir = configDir("my-cli");
|
|
161
|
-
* // → "/home/user/.config/my-cli" (Linux)
|
|
162
|
-
* // → "/Users/user/Library/Application Support/my-cli" (macOS)
|
|
163
|
-
* // → "C:\\Users\\user\\AppData\\Roaming\\my-cli" (Windows)
|
|
164
|
-
* ```
|
|
165
|
-
*/
|
|
166
|
-
declare function configDir(appName: string, env?: PlatformEnv): string;
|
|
167
1
|
/** Supported type literals for store fields. */
|
|
168
2
|
type ValueType = "string" | "number" | "boolean";
|
|
169
3
|
/**
|
|
@@ -175,52 +9,64 @@ type ValueType = "string" | "number" | "boolean";
|
|
|
175
9
|
*/
|
|
176
10
|
type ResolvePrimitive<T extends ValueType> = T extends "string" ? string : T extends "number" ? number : T extends "boolean" ? boolean : never;
|
|
177
11
|
/** Shared fields present on every store field definition. */
|
|
178
|
-
interface FieldDefBase {
|
|
12
|
+
interface FieldDefBase<V> {
|
|
179
13
|
/** Human-readable description for documentation and tooling. */
|
|
180
14
|
description?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional per-field validation function.
|
|
17
|
+
*
|
|
18
|
+
* Called during `read`, `write`, `update`, and `patch` operations when
|
|
19
|
+
* the field has a value (not `undefined`). Throw an error to reject the
|
|
20
|
+
* value.
|
|
21
|
+
*
|
|
22
|
+
* @param value - The field value to validate.
|
|
23
|
+
* @throws When the value is invalid — the error message is captured as a
|
|
24
|
+
* validation issue with the field name as `path`.
|
|
25
|
+
*/
|
|
26
|
+
validate?: (value: V) => void | Promise<void>;
|
|
181
27
|
}
|
|
182
28
|
/** Base for scalar (non-array) fields — `array` must be omitted. */
|
|
183
|
-
interface ScalarFieldBase extends FieldDefBase {
|
|
29
|
+
interface ScalarFieldBase<V> extends FieldDefBase<V> {
|
|
184
30
|
/** Must be omitted for scalar fields — set to `true` for array fields. */
|
|
185
31
|
array?: never;
|
|
186
32
|
}
|
|
187
33
|
/** A scalar string field. */
|
|
188
|
-
interface StringFieldDef extends ScalarFieldBase {
|
|
34
|
+
interface StringFieldDef extends ScalarFieldBase<string> {
|
|
189
35
|
type: "string";
|
|
190
36
|
/** Default string value when the field is not persisted. */
|
|
191
37
|
default?: string;
|
|
192
38
|
}
|
|
193
39
|
/** A scalar number field. */
|
|
194
|
-
interface NumberFieldDef extends ScalarFieldBase {
|
|
40
|
+
interface NumberFieldDef extends ScalarFieldBase<number> {
|
|
195
41
|
type: "number";
|
|
196
42
|
/** Default number value when the field is not persisted. */
|
|
197
43
|
default?: number;
|
|
198
44
|
}
|
|
199
45
|
/** A scalar boolean field. */
|
|
200
|
-
interface BooleanFieldDef extends ScalarFieldBase {
|
|
46
|
+
interface BooleanFieldDef extends ScalarFieldBase<boolean> {
|
|
201
47
|
type: "boolean";
|
|
202
48
|
/** Default boolean value when the field is not persisted. */
|
|
203
49
|
default?: boolean;
|
|
204
50
|
}
|
|
205
51
|
/** Base for array fields — `array` is required as `true`. */
|
|
206
|
-
interface ArrayFieldBase extends FieldDefBase {
|
|
52
|
+
interface ArrayFieldBase<V> extends FieldDefBase<V> {
|
|
207
53
|
/** Collect values into an array. */
|
|
208
54
|
array: true;
|
|
209
55
|
}
|
|
210
56
|
/** An array of strings field. */
|
|
211
|
-
interface StringArrayFieldDef extends ArrayFieldBase {
|
|
57
|
+
interface StringArrayFieldDef extends ArrayFieldBase<string[]> {
|
|
212
58
|
type: "string";
|
|
213
59
|
/** Default string array value when the field is not persisted. */
|
|
214
60
|
default?: readonly string[];
|
|
215
61
|
}
|
|
216
62
|
/** An array of numbers field. */
|
|
217
|
-
interface NumberArrayFieldDef extends ArrayFieldBase {
|
|
63
|
+
interface NumberArrayFieldDef extends ArrayFieldBase<number[]> {
|
|
218
64
|
type: "number";
|
|
219
65
|
/** Default number array value when the field is not persisted. */
|
|
220
66
|
default?: readonly number[];
|
|
221
67
|
}
|
|
222
68
|
/** An array of booleans field. */
|
|
223
|
-
interface BooleanArrayFieldDef extends ArrayFieldBase {
|
|
69
|
+
interface BooleanArrayFieldDef extends ArrayFieldBase<boolean[]> {
|
|
224
70
|
type: "boolean";
|
|
225
71
|
/** Default boolean array value when the field is not persisted. */
|
|
226
72
|
default?: readonly boolean[];
|
|
@@ -229,6 +75,8 @@ interface BooleanArrayFieldDef extends ArrayFieldBase {
|
|
|
229
75
|
* Defines a single field in a store's config schema.
|
|
230
76
|
*
|
|
231
77
|
* Discriminated by `type` and `array` for type-safe `default` values.
|
|
78
|
+
* Each field can optionally include a `validate` function for per-field
|
|
79
|
+
* validation and a `default` value for fallback behavior.
|
|
232
80
|
*
|
|
233
81
|
* @example
|
|
234
82
|
* ```ts
|
|
@@ -237,6 +85,11 @@ interface BooleanArrayFieldDef extends ArrayFieldBase {
|
|
|
237
85
|
* verbose: { type: "boolean", default: false },
|
|
238
86
|
* tags: { type: "string", array: true, default: [] },
|
|
239
87
|
* token: { type: "string" }, // optional — no default
|
|
88
|
+
* port: {
|
|
89
|
+
* type: "number",
|
|
90
|
+
* default: 3000,
|
|
91
|
+
* validate: (v) => { if (v < 1 || v > 65535) throw new Error("invalid port"); },
|
|
92
|
+
* },
|
|
240
93
|
* } satisfies FieldsDef;
|
|
241
94
|
* ```
|
|
242
95
|
*/
|
|
@@ -271,6 +124,15 @@ type InferFieldValue<F extends FieldDef> = F extends {
|
|
|
271
124
|
*/
|
|
272
125
|
type InferStoreConfig<F extends FieldsDef> = { [K in keyof F] : InferFieldValue<F[K]> };
|
|
273
126
|
/**
|
|
127
|
+
* A single validation issue reported during store operations.
|
|
128
|
+
*/
|
|
129
|
+
interface StoreValidatorIssue {
|
|
130
|
+
/** Human-readable description of the validation failure. */
|
|
131
|
+
message: string;
|
|
132
|
+
/** Field name where the issue occurred. */
|
|
133
|
+
path: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
274
136
|
* Options for {@link createStore}.
|
|
275
137
|
*
|
|
276
138
|
* @typeParam F - The field definitions record (inferred via `const` generic).
|
|
@@ -282,6 +144,7 @@ type InferStoreConfig<F extends FieldsDef> = { [K in keyof F] : InferFieldValue<
|
|
|
282
144
|
* fields: {
|
|
283
145
|
* theme: { type: "string", default: "light" },
|
|
284
146
|
* verbose: { type: "boolean", default: false },
|
|
147
|
+
* token: { type: "string" },
|
|
285
148
|
* },
|
|
286
149
|
* });
|
|
287
150
|
* ```
|
|
@@ -290,8 +153,8 @@ interface CreateStoreOptions<F extends FieldsDef> {
|
|
|
290
153
|
/**
|
|
291
154
|
* Absolute directory path where the store JSON file is persisted.
|
|
292
155
|
*
|
|
293
|
-
* Use
|
|
294
|
-
*
|
|
156
|
+
* Use a path helper ({@link configDir}, {@link dataDir}, etc.) to resolve
|
|
157
|
+
* the platform-standard directory from an app name.
|
|
295
158
|
*/
|
|
296
159
|
dirPath: string;
|
|
297
160
|
/**
|
|
@@ -308,14 +171,21 @@ interface CreateStoreOptions<F extends FieldsDef> {
|
|
|
308
171
|
* Field definitions that declare the store's config schema.
|
|
309
172
|
*
|
|
310
173
|
* Each key maps to a {@link FieldDef} with a `type` discriminant and optional
|
|
311
|
-
* `default`. Fields without a `default` are optional (`T | undefined`).
|
|
174
|
+
* `default` and `validate`. Fields without a `default` are optional (`T | undefined`).
|
|
312
175
|
*/
|
|
313
176
|
fields: F;
|
|
177
|
+
/**
|
|
178
|
+
* When `true` (the default), persisted keys not present in `fields`
|
|
179
|
+
* are dropped on read. Set to `false` to preserve unknown keys.
|
|
180
|
+
*
|
|
181
|
+
* @default true
|
|
182
|
+
*/
|
|
183
|
+
pruneUnknown?: boolean;
|
|
314
184
|
}
|
|
315
185
|
/**
|
|
316
186
|
* Receives the current effective config and returns an updated config.
|
|
317
187
|
*
|
|
318
|
-
* Used by {@link Store.update} to apply
|
|
188
|
+
* Used by {@link Store.update} to apply mutations atomically.
|
|
319
189
|
*
|
|
320
190
|
* @example
|
|
321
191
|
* ```ts
|
|
@@ -329,8 +199,8 @@ type StoreUpdater<TConfig> = (current: TConfig) => NoInfer<TConfig>;
|
|
|
329
199
|
/**
|
|
330
200
|
* A typed async config store returned by {@link createStore}.
|
|
331
201
|
*
|
|
332
|
-
* Provides `read`, `write`, `update`, and `reset` operations for a
|
|
333
|
-
* typed config object persisted as JSON on the local filesystem.
|
|
202
|
+
* Provides `read`, `write`, `update`, `patch`, and `reset` operations for a
|
|
203
|
+
* single typed config object persisted as JSON on the local filesystem.
|
|
334
204
|
*
|
|
335
205
|
* @typeParam TConfig - The inferred config shape from field definitions.
|
|
336
206
|
*
|
|
@@ -340,12 +210,14 @@ type StoreUpdater<TConfig> = (current: TConfig) => NoInfer<TConfig>;
|
|
|
340
210
|
* dirPath: configDir("my-cli"),
|
|
341
211
|
* fields: {
|
|
342
212
|
* theme: { type: "string", default: "light" },
|
|
213
|
+
* verbose: { type: "boolean", default: false },
|
|
343
214
|
* },
|
|
344
215
|
* });
|
|
345
216
|
*
|
|
346
217
|
* const config = await store.read();
|
|
347
|
-
* await store.write({ theme: "dark" });
|
|
218
|
+
* await store.write({ theme: "dark", verbose: true });
|
|
348
219
|
* await store.update((c) => ({ ...c, theme: "dark" }));
|
|
220
|
+
* await store.patch({ theme: "dark" });
|
|
349
221
|
* await store.reset();
|
|
350
222
|
* ```
|
|
351
223
|
*/
|
|
@@ -356,15 +228,23 @@ interface Store<TConfig> {
|
|
|
356
228
|
* Always returns a value — fields with defaults are guaranteed present,
|
|
357
229
|
* fields without defaults may be `undefined`.
|
|
358
230
|
*
|
|
231
|
+
* When fields have `validate` functions, the merged config is validated
|
|
232
|
+
* after defaults are applied. Invalid persisted config fails loudly.
|
|
233
|
+
*
|
|
359
234
|
* @returns The effective config value.
|
|
360
235
|
* @throws {CrustStoreError} `PARSE` if persisted JSON is malformed.
|
|
236
|
+
* @throws {CrustStoreError} `VALIDATION` if field validation fails.
|
|
361
237
|
* @throws {CrustStoreError} `IO` on filesystem read failures.
|
|
362
238
|
*/
|
|
363
239
|
read(): Promise<TConfig>;
|
|
364
240
|
/**
|
|
365
241
|
* Atomically persists the full config object.
|
|
366
242
|
*
|
|
243
|
+
* When fields have `validate` functions, the config is validated before
|
|
244
|
+
* persistence.
|
|
245
|
+
*
|
|
367
246
|
* @param config - The complete config to persist.
|
|
247
|
+
* @throws {CrustStoreError} `VALIDATION` if field validation fails.
|
|
368
248
|
* @throws {CrustStoreError} `IO` on filesystem write failures.
|
|
369
249
|
*/
|
|
370
250
|
write(config: NoInfer<TConfig>): Promise<void>;
|
|
@@ -373,17 +253,294 @@ interface Store<TConfig> {
|
|
|
373
253
|
*
|
|
374
254
|
* @param updater - Function receiving current config and returning updated config.
|
|
375
255
|
* @throws {CrustStoreError} `PARSE` if persisted JSON is malformed.
|
|
256
|
+
* @throws {CrustStoreError} `VALIDATION` if field validation fails.
|
|
376
257
|
* @throws {CrustStoreError} `IO` on filesystem failures.
|
|
377
258
|
*/
|
|
378
259
|
update(updater: StoreUpdater<TConfig>): Promise<void>;
|
|
379
260
|
/**
|
|
380
|
-
*
|
|
261
|
+
* Applies a partial update to the current config and persists.
|
|
262
|
+
*
|
|
263
|
+
* Only the provided keys are updated; everything else is preserved.
|
|
264
|
+
*
|
|
265
|
+
* @param partial - A partial subset of the config to merge in.
|
|
266
|
+
* @throws {CrustStoreError} `VALIDATION` if field validation fails.
|
|
267
|
+
* @throws {CrustStoreError} `PARSE` if persisted JSON is malformed.
|
|
268
|
+
* @throws {CrustStoreError} `IO` on filesystem failures.
|
|
269
|
+
*/
|
|
270
|
+
patch(partial: Partial<NoInfer<TConfig>>): Promise<void>;
|
|
271
|
+
/**
|
|
272
|
+
* Removes the persisted config file, returning the store to
|
|
273
|
+
* defaults-on-read behavior.
|
|
381
274
|
*
|
|
382
275
|
* @throws {CrustStoreError} `IO` on filesystem deletion failures.
|
|
383
276
|
*/
|
|
384
277
|
reset(): Promise<void>;
|
|
385
278
|
}
|
|
386
279
|
/**
|
|
280
|
+
* Contextual details attached to a `PATH` error.
|
|
281
|
+
*
|
|
282
|
+
* Returned when the resolved or provided config file path is invalid.
|
|
283
|
+
*/
|
|
284
|
+
interface PathErrorDetails {
|
|
285
|
+
/** The path value that caused the error. */
|
|
286
|
+
path: string;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Contextual details attached to a `PARSE` error.
|
|
290
|
+
*
|
|
291
|
+
* Returned when the persisted JSON file cannot be parsed.
|
|
292
|
+
*/
|
|
293
|
+
interface ParseErrorDetails {
|
|
294
|
+
/** Absolute path to the file that failed parsing. */
|
|
295
|
+
path: string;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Contextual details attached to an `IO` error.
|
|
299
|
+
*
|
|
300
|
+
* Returned on filesystem read, write, or delete failures.
|
|
301
|
+
*/
|
|
302
|
+
interface IOErrorDetails {
|
|
303
|
+
/** Absolute path to the file involved in the failed operation. */
|
|
304
|
+
path: string;
|
|
305
|
+
/** The filesystem operation that failed (e.g. `"read"`, `"write"`, `"delete"`). */
|
|
306
|
+
operation: "read" | "write" | "delete";
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Contextual details attached to a `VALIDATION` error.
|
|
310
|
+
*
|
|
311
|
+
* Returned when a store validator rejects a config object during
|
|
312
|
+
* read, write, update, or patch operations.
|
|
313
|
+
*/
|
|
314
|
+
interface ValidationErrorDetails {
|
|
315
|
+
/** The store operation during which validation failed. */
|
|
316
|
+
operation: "read" | "write" | "update" | "patch";
|
|
317
|
+
/** Structured validation issues for programmatic handling. */
|
|
318
|
+
issues: readonly StoreValidatorIssue[];
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Maps each {@link StoreErrorCode} to its structured details type.
|
|
322
|
+
*
|
|
323
|
+
* Used internally for conditional constructor parameters and type narrowing.
|
|
324
|
+
*/
|
|
325
|
+
interface StoreErrorDetailsMap {
|
|
326
|
+
PATH: PathErrorDetails;
|
|
327
|
+
PARSE: ParseErrorDetails;
|
|
328
|
+
IO: IOErrorDetails;
|
|
329
|
+
VALIDATION: ValidationErrorDetails;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* All possible error codes emitted by `@crustjs/store`.
|
|
333
|
+
*
|
|
334
|
+
* - `PATH` — Invalid or unsupported config file path
|
|
335
|
+
* - `PARSE` — Malformed JSON in persisted config file
|
|
336
|
+
* - `IO` — Filesystem read, write, or delete failure
|
|
337
|
+
* - `VALIDATION` — Config object rejected by a store validator
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* import { CrustStoreError } from "@crustjs/store";
|
|
342
|
+
*
|
|
343
|
+
* try {
|
|
344
|
+
* const config = await store.read();
|
|
345
|
+
* } catch (err) {
|
|
346
|
+
* if (err instanceof CrustStoreError) {
|
|
347
|
+
* switch (err.code) {
|
|
348
|
+
* case "PARSE":
|
|
349
|
+
* console.error(`Corrupt config at ${err.details.path}`);
|
|
350
|
+
* break;
|
|
351
|
+
* case "VALIDATION":
|
|
352
|
+
* for (const issue of err.details.issues) {
|
|
353
|
+
* console.error(`${issue.path}: ${issue.message}`);
|
|
354
|
+
* }
|
|
355
|
+
* break;
|
|
356
|
+
* case "IO":
|
|
357
|
+
* console.error(`File ${err.details.operation} failed: ${err.message}`);
|
|
358
|
+
* break;
|
|
359
|
+
* }
|
|
360
|
+
* }
|
|
361
|
+
* }
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
type StoreErrorCode = keyof StoreErrorDetailsMap;
|
|
365
|
+
/** Resolves the details type for a given {@link StoreErrorCode}. */
|
|
366
|
+
type StoreErrorDetails<C extends StoreErrorCode> = StoreErrorDetailsMap[C];
|
|
367
|
+
/**
|
|
368
|
+
* A typed error thrown by `@crustjs/store` for path, parse, IO, and validation failures.
|
|
369
|
+
*
|
|
370
|
+
* Every `CrustStoreError` carries a {@link StoreErrorCode} that identifies the failure
|
|
371
|
+
* category, along with structured {@link StoreErrorDetails} for programmatic handling
|
|
372
|
+
* without fragile message parsing.
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```ts
|
|
376
|
+
* import { CrustStoreError } from "@crustjs/store";
|
|
377
|
+
*
|
|
378
|
+
* try {
|
|
379
|
+
* const config = await store.read();
|
|
380
|
+
* } catch (err) {
|
|
381
|
+
* if (err instanceof CrustStoreError && err.is("PARSE")) {
|
|
382
|
+
* console.error(`Corrupt config at ${err.details.path}`);
|
|
383
|
+
* }
|
|
384
|
+
* }
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
declare class CrustStoreError<C extends StoreErrorCode = StoreErrorCode> extends Error {
|
|
388
|
+
/** Machine-readable error code for programmatic handling. */
|
|
389
|
+
readonly code: C;
|
|
390
|
+
/** Structured payload with context about the failure. */
|
|
391
|
+
readonly details: StoreErrorDetails<C>;
|
|
392
|
+
/** Optional wrapped original error or value. */
|
|
393
|
+
cause?: unknown;
|
|
394
|
+
constructor(code: C, message: string, details: StoreErrorDetails<C>);
|
|
395
|
+
/**
|
|
396
|
+
* Type-guard that narrows this error to a specific {@link StoreErrorCode}.
|
|
397
|
+
*
|
|
398
|
+
* When the guard returns `true`, TypeScript narrows the `details` property
|
|
399
|
+
* to the corresponding details type.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* if (err.is("IO")) {
|
|
404
|
+
* // err.details is IOErrorDetails
|
|
405
|
+
* console.error(err.details.operation, err.details.path);
|
|
406
|
+
* }
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
is<T extends StoreErrorCode>(code: T): this is CrustStoreError<T>;
|
|
410
|
+
/**
|
|
411
|
+
* Attaches a cause to this error for stack-trace chaining.
|
|
412
|
+
*
|
|
413
|
+
* Returns `this` for fluent usage.
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```ts
|
|
417
|
+
* throw new CrustStoreError("IO", "write failed", {
|
|
418
|
+
* path: "/path/to/config.json",
|
|
419
|
+
* operation: "write",
|
|
420
|
+
* }).withCause(originalError);
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
withCause(cause: unknown): this;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Encapsulates runtime environment values needed for path resolution.
|
|
427
|
+
*
|
|
428
|
+
* Exposed as a parameter to allow deterministic testing without mutating
|
|
429
|
+
* `process.env` or `process.platform`.
|
|
430
|
+
*/
|
|
431
|
+
interface PlatformEnv {
|
|
432
|
+
/** Operating system platform identifier (e.g. `"linux"`, `"darwin"`, `"win32"`). */
|
|
433
|
+
platform: string;
|
|
434
|
+
/** Environment variables map. */
|
|
435
|
+
env: Record<string, string | undefined>;
|
|
436
|
+
/** User home directory path. */
|
|
437
|
+
homedir: string;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Resolves the platform-standard config directory for the given app.
|
|
441
|
+
*
|
|
442
|
+
* Platform conventions:
|
|
443
|
+
* - **Linux / macOS**: `$XDG_CONFIG_HOME/<appName>` or `~/.config/<appName>`
|
|
444
|
+
* - **Windows**: `%APPDATA%/<appName>` or `~/AppData/Roaming/<appName>`
|
|
445
|
+
*
|
|
446
|
+
* macOS uses XDG conventions for consistency with Linux.
|
|
447
|
+
*
|
|
448
|
+
* @param appName - Application name used as directory name. Must be a non-empty
|
|
449
|
+
* string without path separators.
|
|
450
|
+
* @param env - Optional platform environment override for testing.
|
|
451
|
+
* @returns Absolute path to the app's config directory.
|
|
452
|
+
* @throws {CrustStoreError} `PATH` if `appName` is invalid or platform is unsupported.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```ts
|
|
456
|
+
* import { configDir } from "@crustjs/store";
|
|
457
|
+
*
|
|
458
|
+
* const dir = configDir("my-cli");
|
|
459
|
+
* // → "/home/user/.config/my-cli" (Linux)
|
|
460
|
+
* // → "/Users/user/.config/my-cli" (macOS)
|
|
461
|
+
* // → "C:\\Users\\user\\AppData\\Roaming\\my-cli" (Windows)
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
declare function configDir(appName: string, env?: PlatformEnv): string;
|
|
465
|
+
/**
|
|
466
|
+
* Resolves the platform-standard data directory for the given app.
|
|
467
|
+
*
|
|
468
|
+
* Platform conventions:
|
|
469
|
+
* - **Linux / macOS**: `$XDG_DATA_HOME/<appName>` or `~/.local/share/<appName>`
|
|
470
|
+
* - **Windows**: `%LOCALAPPDATA%/<appName>/Data` or `~/AppData/Local/<appName>/Data`
|
|
471
|
+
*
|
|
472
|
+
* macOS uses XDG conventions for consistency with Linux.
|
|
473
|
+
*
|
|
474
|
+
* @param appName - Application name used as directory name. Must be a non-empty
|
|
475
|
+
* string without path separators.
|
|
476
|
+
* @param env - Optional platform environment override for testing.
|
|
477
|
+
* @returns Absolute path to the app's data directory.
|
|
478
|
+
* @throws {CrustStoreError} `PATH` if `appName` is invalid or platform is unsupported.
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```ts
|
|
482
|
+
* import { dataDir } from "@crustjs/store";
|
|
483
|
+
*
|
|
484
|
+
* const dir = dataDir("my-cli");
|
|
485
|
+
* // → "/home/user/.local/share/my-cli" (Linux)
|
|
486
|
+
* // → "/Users/user/.local/share/my-cli" (macOS)
|
|
487
|
+
* // → "C:\\Users\\user\\AppData\\Local\\my-cli\\Data" (Windows)
|
|
488
|
+
* ```
|
|
489
|
+
*/
|
|
490
|
+
declare function dataDir(appName: string, env?: PlatformEnv): string;
|
|
491
|
+
/**
|
|
492
|
+
* Resolves the platform-standard state directory for the given app.
|
|
493
|
+
*
|
|
494
|
+
* Platform conventions:
|
|
495
|
+
* - **Linux / macOS**: `$XDG_STATE_HOME/<appName>` or `~/.local/state/<appName>`
|
|
496
|
+
* - **Windows**: `%LOCALAPPDATA%/<appName>/State` or `~/AppData/Local/<appName>/State`
|
|
497
|
+
*
|
|
498
|
+
* macOS uses XDG conventions for consistency with Linux.
|
|
499
|
+
*
|
|
500
|
+
* @param appName - Application name used as directory name. Must be a non-empty
|
|
501
|
+
* string without path separators.
|
|
502
|
+
* @param env - Optional platform environment override for testing.
|
|
503
|
+
* @returns Absolute path to the app's state directory.
|
|
504
|
+
* @throws {CrustStoreError} `PATH` if `appName` is invalid or platform is unsupported.
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```ts
|
|
508
|
+
* import { stateDir } from "@crustjs/store";
|
|
509
|
+
*
|
|
510
|
+
* const dir = stateDir("my-cli");
|
|
511
|
+
* // → "/home/user/.local/state/my-cli" (Linux)
|
|
512
|
+
* // → "/Users/user/.local/state/my-cli" (macOS)
|
|
513
|
+
* // → "C:\\Users\\user\\AppData\\Local\\my-cli\\State" (Windows)
|
|
514
|
+
* ```
|
|
515
|
+
*/
|
|
516
|
+
declare function stateDir(appName: string, env?: PlatformEnv): string;
|
|
517
|
+
/**
|
|
518
|
+
* Resolves the platform-standard cache directory for the given app.
|
|
519
|
+
*
|
|
520
|
+
* Platform conventions:
|
|
521
|
+
* - **Linux / macOS**: `$XDG_CACHE_HOME/<appName>` or `~/.cache/<appName>`
|
|
522
|
+
* - **Windows**: `%LOCALAPPDATA%/<appName>/Cache` or `~/AppData/Local/<appName>/Cache`
|
|
523
|
+
*
|
|
524
|
+
* macOS uses XDG conventions for consistency with Linux.
|
|
525
|
+
*
|
|
526
|
+
* @param appName - Application name used as directory name. Must be a non-empty
|
|
527
|
+
* string without path separators.
|
|
528
|
+
* @param env - Optional platform environment override for testing.
|
|
529
|
+
* @returns Absolute path to the app's cache directory.
|
|
530
|
+
* @throws {CrustStoreError} `PATH` if `appName` is invalid or platform is unsupported.
|
|
531
|
+
*
|
|
532
|
+
* @example
|
|
533
|
+
* ```ts
|
|
534
|
+
* import { cacheDir } from "@crustjs/store";
|
|
535
|
+
*
|
|
536
|
+
* const dir = cacheDir("my-cli");
|
|
537
|
+
* // → "/home/user/.cache/my-cli" (Linux)
|
|
538
|
+
* // → "/Users/user/.cache/my-cli" (macOS)
|
|
539
|
+
* // → "C:\\Users\\user\\AppData\\Local\\my-cli\\Cache" (Windows)
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
declare function cacheDir(appName: string, env?: PlatformEnv): string;
|
|
543
|
+
/**
|
|
387
544
|
* Creates a typed async config store backed by a local JSON file.
|
|
388
545
|
*
|
|
389
546
|
* The store resolves its file path once at creation time from `dirPath` and
|
|
@@ -393,7 +550,7 @@ interface Store<TConfig> {
|
|
|
393
550
|
*
|
|
394
551
|
* @typeParam F - Field definitions record (inferred via `const` generic).
|
|
395
552
|
* @param options - Store configuration options.
|
|
396
|
-
* @returns A {@link Store} instance with `read`, `write`, `update`, and `reset` methods.
|
|
553
|
+
* @returns A {@link Store} instance with `read`, `write`, `update`, `patch`, and `reset` methods.
|
|
397
554
|
* @throws {CrustStoreError} `PATH` if `dirPath` or `name` is invalid.
|
|
398
555
|
*
|
|
399
556
|
* @example
|
|
@@ -410,12 +567,13 @@ interface Store<TConfig> {
|
|
|
410
567
|
* });
|
|
411
568
|
*
|
|
412
569
|
* const config = await store.read();
|
|
413
|
-
* // → { theme: "light", verbose: false
|
|
570
|
+
* // → { theme: "light", verbose: false, token: undefined }
|
|
414
571
|
*
|
|
415
572
|
* await store.write({ theme: "dark", verbose: true, token: "abc" });
|
|
416
573
|
* await store.update((c) => ({ ...c, theme: "light" }));
|
|
574
|
+
* await store.patch({ theme: "solarized" });
|
|
417
575
|
* await store.reset();
|
|
418
576
|
* ```
|
|
419
577
|
*/
|
|
420
578
|
declare function createStore<const F extends FieldsDef>(options: CreateStoreOptions<F>): Store<InferStoreConfig<F>>;
|
|
421
|
-
export { createStore, configDir, ValueType, StoreUpdater, StoreErrorCode, Store, PlatformEnv, InferStoreConfig, FieldsDef, FieldDef, CrustStoreError, CreateStoreOptions };
|
|
579
|
+
export { stateDir, dataDir, createStore, configDir, cacheDir, ValueType, ValidationErrorDetails, StoreValidatorIssue, StoreUpdater, StoreErrorCode, Store, PlatformEnv, InferStoreConfig, FieldsDef, FieldDef, CrustStoreError, CreateStoreOptions };
|