@rezwanul7/settings 0.0.0-stage → 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,348 @@
1
+ import { O as Owner, S as SettingsStore, a as StoredOverrides } from './store-B4pWek75.cjs';
2
+
3
+ /** The Standard Schema interface. */
4
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
5
+ /** The Standard Schema properties. */
6
+ readonly "~standard": StandardSchemaV1.Props<Input, Output>;
7
+ }
8
+ declare namespace StandardSchemaV1 {
9
+ /** The Standard Schema properties interface. */
10
+ interface Props<Input = unknown, Output = Input> {
11
+ /** The version number of the standard. */
12
+ readonly version: 1;
13
+ /** The vendor name of the schema library. */
14
+ readonly vendor: string;
15
+ /** Validates unknown input values. */
16
+ readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
17
+ /** Inferred types associated with the schema. */
18
+ readonly types?: Types<Input, Output> | undefined;
19
+ }
20
+ /** The result interface of the validate function. */
21
+ type Result<Output> = SuccessResult<Output> | FailureResult;
22
+ /** The result interface if validation succeeds. */
23
+ interface SuccessResult<Output> {
24
+ /** The typed output value. */
25
+ readonly value: Output;
26
+ /** A falsy value for `issues` indicates success. */
27
+ readonly issues?: undefined;
28
+ }
29
+ /** The options for the validate function. */
30
+ interface Options {
31
+ /** Explicit support for additional vendor-specific parameters, if needed. */
32
+ readonly libraryOptions?: Record<string, unknown> | undefined;
33
+ }
34
+ /** The result interface if validation fails. */
35
+ interface FailureResult {
36
+ /** The issues of failed validation. */
37
+ readonly issues: ReadonlyArray<Issue>;
38
+ }
39
+ /** The issue interface of the failure output. */
40
+ interface Issue {
41
+ /** The error message of the issue. */
42
+ readonly message: string;
43
+ /** The path of the issue, if any. */
44
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
45
+ }
46
+ /** The path segment interface of the issue. */
47
+ interface PathSegment {
48
+ /** The key representing a path segment. */
49
+ readonly key: PropertyKey;
50
+ }
51
+ /** The Standard Schema types interface. */
52
+ interface Types<Input = unknown, Output = Input> {
53
+ /** The input type of the schema. */
54
+ readonly input: Input;
55
+ /** The output type of the schema. */
56
+ readonly output: Output;
57
+ }
58
+ /** Infers the input type of a Standard Schema. */
59
+ type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["input"];
60
+ /** Infers the output type of a Standard Schema. */
61
+ type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["output"];
62
+ }
63
+
64
+ /**
65
+ * A Scope's schema and its defaults: the shape every Owner's Effective settings must have, and the values used where
66
+ * an Owner has no Override. Create one with {@link defineSettings}.
67
+ *
68
+ * @typeParam T - The settings type: the schema's output type.
69
+ * @typeParam SingleOwner - `true` for a single-owner Scope, such as `global`, whose one Owner takes no id.
70
+ */
71
+ interface SettingsDefinition<T = unknown, SingleOwner extends boolean = boolean> {
72
+ /** The Standard Schema every Owner's Effective settings must pass. */
73
+ readonly schema: StandardSchemaV1<unknown, T>;
74
+ /** The complete settings used where an Owner has no Override. */
75
+ readonly defaults: T;
76
+ /** Whether the Scope has exactly one Owner, stored with the id `"default"`. */
77
+ readonly singleOwner: SingleOwner;
78
+ }
79
+ /**
80
+ * Declares a Scope's Settings definition. The settings type is inferred from the schema's output type, and
81
+ * `defaults` must be a complete value of it; optional schema fields may be left out. Any Standard Schema works, such
82
+ * as one from Zod, Valibot or ArkType.
83
+ *
84
+ * The defaults are checked against the schema on the first operation on the Scope, because Standard Schema
85
+ * validation may be async; `createSettings` throws `SettingsDefinitionError` there if they fail.
86
+ *
87
+ * @param definition - The schema, the defaults, and `singleOwner: true` for a Scope with exactly one Owner.
88
+ * @returns The Settings definition, to pass to `createSettings` under its Scope name.
89
+ *
90
+ * @example
91
+ * const user = defineSettings({
92
+ * schema: z.object({ theme: z.enum(["light", "dark"]), language: z.string() }),
93
+ * defaults: { theme: "light", language: "en" },
94
+ * });
95
+ * const global = defineSettings({
96
+ * schema: z.object({ maintenanceBanner: z.string().nullable() }),
97
+ * defaults: { maintenanceBanner: null },
98
+ * singleOwner: true,
99
+ * });
100
+ */
101
+ declare function defineSettings<Schema extends StandardSchemaV1, SingleOwner extends boolean = false>(definition: {
102
+ /** The Standard Schema every Owner's Effective settings must pass. */
103
+ readonly schema: Schema;
104
+ /** The complete settings used where an Owner has no Override. */
105
+ readonly defaults: StandardSchemaV1.InferOutput<Schema>;
106
+ /** `true` for a Scope with exactly one Owner, which takes no id. Defaults to `false`. */
107
+ readonly singleOwner?: SingleOwner;
108
+ }): SettingsDefinition<StandardSchemaV1.InferOutput<Schema>, SingleOwner>;
109
+
110
+ /** Objects whose keys a dot path walks through; arrays, functions and dates are values, never walked into. */
111
+ type Walkable<T> = T extends readonly unknown[] | Date | ((...args: never[]) => unknown) ? never : T extends object ? T : never;
112
+ /**
113
+ * The union of dot paths through the nested plain objects of `T`, such as `"theme" | "notifications" |
114
+ * "notifications.email"`. A path never descends into an array: an array is one value, set and reset as a whole.
115
+ *
116
+ * @typeParam T - The settings type.
117
+ */
118
+ type Path<T> = [Walkable<T>] extends [never] ? never : {
119
+ [K in keyof Walkable<T> & string]-?: K | `${K}.${Path<NonNullable<Walkable<T>[K]>>}`;
120
+ }[keyof Walkable<T> & string];
121
+ /**
122
+ * The type of the value at dot path `P` in `T`. A path through an optional or nullable object can find nothing, so
123
+ * its value type includes `undefined`.
124
+ *
125
+ * @typeParam T - The settings type.
126
+ * @typeParam P - A dot path, one of {@link Path}.
127
+ */
128
+ type PathValue<T, P extends string> = P extends keyof Walkable<T> ? Walkable<T>[P] : P extends `${infer K}.${infer Rest}` ? K extends keyof Walkable<T> ? PathValue<NonNullable<Walkable<T>[K]>, Rest> | (null extends Walkable<T>[K] ? undefined : never) | (undefined extends Walkable<T>[K] ? undefined : never) : never : never;
129
+ /**
130
+ * A partial of `T` at any depth, for `update`: every key of a nested plain object may be left out or `undefined`.
131
+ * Arrays, dates and functions are whole values, never partial.
132
+ *
133
+ * @typeParam T - The settings type.
134
+ */
135
+ type DeepPartial<T> = T extends readonly unknown[] | Date | ((...args: never[]) => unknown) ? T : T extends object ? {
136
+ [K in keyof T]?: DeepPartial<T[K]> | undefined;
137
+ } : T;
138
+
139
+ /**
140
+ * Options for {@link createSettings}.
141
+ *
142
+ * @typeParam Definitions - The Settings definitions by Scope name.
143
+ */
144
+ interface SettingsOptions<Definitions extends Record<string, SettingsDefinition>> {
145
+ /** Each Scope's Settings definition, by Scope name, such as `{ user, global }`. */
146
+ readonly definitions: Definitions;
147
+ /** Where the Overrides are kept. */
148
+ readonly store: SettingsStore;
149
+ /**
150
+ * Called when stored Overrides fail the current schema, for example after an enum value was renamed, or are keys the
151
+ * schema no longer knows, and are ignored so their defaults apply. The stored document isn't changed by a read; the
152
+ * next successful write drops them. Writes read the Overrides first, so it can also be called during a write, again
153
+ * on each retry. Without it, ignored Overrides are dropped silently.
154
+ *
155
+ * @param owner - The Owner whose Overrides were ignored.
156
+ * @param issues - The Standard Schema issues the Effective settings failed with, naming the ignored paths.
157
+ */
158
+ readonly onInvalid?: (owner: Owner, issues: ReadonlyArray<StandardSchemaV1.Issue>) => void;
159
+ /**
160
+ * How many times a write that lost the version race to another write re-reads the Overrides and applies its change
161
+ * again before it throws {@link SettingsConflictError}. A whole number, 0 or more; 0 never retries.
162
+ *
163
+ * @defaultValue 3
164
+ */
165
+ readonly maxRetries?: number;
166
+ }
167
+ /**
168
+ * Reads and writes one Owner's settings. Get one from `settings.scope(name, id)`.
169
+ *
170
+ * A stored Override that fails the current schema, or a stored key the schema no longer knows, is ignored on read so
171
+ * its default applies, and is reported to `onInvalid`; the stored document isn't changed by a read.
172
+ *
173
+ * Every write reads the Owner's Overrides, drops the ones a read would ignore, applies the change, validates the
174
+ * Effective settings the change produces against the Scope's schema, and stores the Overrides with the version it
175
+ * read. Only that result is validated, so a write that repairs Effective settings a read throws
176
+ * {@link SettingsDefinitionError} for succeeds. If another write stored a newer version in between, it reads again
177
+ * and applies its change to the newer Overrides, so concurrent writes to different settings are all kept. What is
178
+ * stored is the change as given, not the schema's output.
179
+ *
180
+ * The keys `__proto__`, `constructor` and `prototype` can't be setting names: a write with one in its path or value
181
+ * throws {@link SettingsValidationError} with a `"Forbidden key"` issue, and `get(path)` reads one as `undefined`.
182
+ *
183
+ * @typeParam T - The Scope's settings type.
184
+ */
185
+ interface ScopedSettings<T> {
186
+ /** The Owner these settings belong to. */
187
+ readonly owner: Owner;
188
+ /**
189
+ * Reads the Owner's Effective settings: the Scope's defaults with the Owner's Overrides deep-merged over them.
190
+ * Plain objects merge key by key; arrays, scalars and `null` replace the default. Overrides that fail the schema
191
+ * are ignored and reported to `onInvalid`.
192
+ *
193
+ * @returns The complete Effective settings, as the schema outputs them.
194
+ * @throws {@link SettingsDefinitionError} If the Scope's defaults fail its schema, or the Effective settings still
195
+ * fail it after ignoring the invalid Overrides.
196
+ */
197
+ get(): Promise<T>;
198
+ /**
199
+ * Reads one value of the Owner's Effective settings.
200
+ *
201
+ * @param path - A dot path into the settings, such as `"notifications.email"`.
202
+ * @returns The Effective value at `path`.
203
+ * @throws {@link SettingsDefinitionError} If the Scope's defaults or the Effective settings fail its schema.
204
+ */
205
+ get<P extends Path<T>>(path: P): Promise<PathValue<T, P>>;
206
+ /**
207
+ * Stores `value` as the Owner's Override at `path`, keeping its other Overrides. A value equal to the default is
208
+ * still stored, so a later change of the default doesn't change what the Owner chose.
209
+ *
210
+ * @param path - A dot path into the settings, such as `"notifications.email"`.
211
+ * @param value - The value to store at `path`.
212
+ * @throws {@link SettingsValidationError} If the Effective settings with the change fail the schema, or the change has
213
+ * a key the schema doesn't know, reported as an `"Unknown setting"` issue; nothing is stored.
214
+ * @throws {@link SettingsConflictError} If other writes to the Owner kept winning the version race for `maxRetries`
215
+ * retries; nothing is stored.
216
+ */
217
+ set<P extends Path<T>>(path: P, value: PathValue<T, P>): Promise<void>;
218
+ /**
219
+ * Changes several settings at once by deep-merging `partial` into the Owner's Overrides: plain objects merge key by
220
+ * key; arrays, scalars and `null` replace. An `undefined` value leaves that Override as it was, while `null` is
221
+ * stored as a value and passes only if the schema allows it.
222
+ *
223
+ * @param partial - The settings to change, at any depth, such as `{ theme: "dark", notifications: { email: false } }`.
224
+ * @throws {@link SettingsValidationError} If the Effective settings with the change fail the schema, or `partial` has
225
+ * a key the schema doesn't know, reported as an `"Unknown setting"` issue; nothing is stored.
226
+ * @throws {@link SettingsConflictError} If other writes to the Owner kept winning the version race for `maxRetries`
227
+ * retries; nothing is stored.
228
+ */
229
+ update(partial: DeepPartial<T>): Promise<void>;
230
+ /**
231
+ * Reads the Owner's Overrides as stored, without the defaults, for example to show which values are the Owner's
232
+ * own choices. Overrides a read would ignore are included until the next write drops them.
233
+ *
234
+ * @returns The stored Overrides, or `{}` when nothing is stored.
235
+ */
236
+ getOverrides(): Promise<Record<string, unknown>>;
237
+ /**
238
+ * Removes the Override at `path`, so that setting follows the default again. Resetting an object removes every
239
+ * Override under it. With no Override at or under `path`, it writes nothing.
240
+ *
241
+ * @param path - A dot path into the settings.
242
+ * @throws {@link SettingsValidationError} If the Effective settings without the Override fail the schema.
243
+ * @throws {@link SettingsConflictError} If other writes to the Owner kept winning the version race for `maxRetries`
244
+ * retries; nothing is stored.
245
+ */
246
+ reset(path: Path<T>): Promise<void>;
247
+ /**
248
+ * Deletes everything stored for the Owner, so it reads the defaults. Use it to clean up when the Owner is deleted.
249
+ */
250
+ reset(): Promise<void>;
251
+ }
252
+ /** The settings type of a Settings definition. */
253
+ type SettingsOf<D> = D extends SettingsDefinition<infer T> ? T : never;
254
+ /** The id argument of `scope()`: none for a single-owner Scope, a string for any other. */
255
+ type OwnerIdArgs<D> = D extends SettingsDefinition<unknown, true> ? [] : [id: string];
256
+ /**
257
+ * Creates the settings for an app's Scopes over one Store. Call `scope(name, id)` on the result to read and write
258
+ * one Owner's settings; a single-owner Scope takes no id.
259
+ *
260
+ * Each Scope's defaults are validated against its schema on the first operation on that Scope, and the outcome is
261
+ * kept for the life of the returned object. If the validation itself throws, such as an async validator that can't
262
+ * reach its service, that operation rejects with the error and the next one checks again.
263
+ *
264
+ * @param options - The Settings definitions by Scope name, the Store, and how often a conflicting write retries.
265
+ * @returns An object whose `scope(name, id)` returns the {@link ScopedSettings} of one Owner.
266
+ * @throws `RangeError` If `maxRetries` isn't a whole number, 0 or more.
267
+ *
268
+ * @example
269
+ * const settings = createSettings({ definitions: { user, global }, store: new MemoryStore() });
270
+ * const theme = await settings.scope("user", "1").get("theme");
271
+ * await settings.scope("global").set("maintenanceBanner", "Back at 10:00");
272
+ */
273
+ declare function createSettings<Definitions extends Record<string, SettingsDefinition>>(options: SettingsOptions<Definitions>): {
274
+ /**
275
+ * Returns the settings of one Owner of the Scope `name`.
276
+ *
277
+ * @param name - The Scope name, a key of `definitions`.
278
+ * @param id - The Owner's id; left out for a single-owner Scope.
279
+ * @throws {@link SettingsDefinitionError} If `name` isn't a key of `definitions`.
280
+ */
281
+ scope<Name extends keyof Definitions & string>(name: Name, ...id: OwnerIdArgs<Definitions[Name]>): ScopedSettings<SettingsOf<Definitions[Name]>>;
282
+ };
283
+
284
+ /**
285
+ * A Store in process memory, for tests and prototypes. It keeps Overrides as JSON, the way a database's JSON column
286
+ * does, so a value that isn't JSON, such as a `Date`, reads back as its JSON form here too. It copies Overrides on the
287
+ * way in and out, so callers can't change what is stored by mutating an object they passed or got back. Its contents
288
+ * are lost when the process exits and aren't shared between processes. Versions keep increasing across `delete`, so a
289
+ * write that read a version before a delete is a conflict; the Store remembers one number per Owner it ever stored.
290
+ *
291
+ * @example
292
+ * const settings = createSettings({ definitions: { user }, store: new MemoryStore() });
293
+ */
294
+ declare class MemoryStore implements SettingsStore {
295
+ #private;
296
+ /** {@inheritDoc SettingsStore.read} */
297
+ read(owner: Owner): Promise<StoredOverrides | null>;
298
+ /** {@inheritDoc SettingsStore.write} */
299
+ write(owner: Owner, overrides: Record<string, unknown>, expectedVersion: number | null): Promise<number | null>;
300
+ /** {@inheritDoc SettingsStore.delete} */
301
+ delete(owner: Owner): Promise<void>;
302
+ }
303
+
304
+ /**
305
+ * A write would make an Owner's Effective settings fail its Scope's schema, so nothing was stored. `issues` are the
306
+ * Standard Schema issues, with paths into the Effective settings, for showing next to a form field.
307
+ */
308
+ declare class SettingsValidationError extends Error {
309
+ /** The Standard Schema issues the Effective settings failed with. */
310
+ readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
311
+ /**
312
+ * @param issues - The Standard Schema issues.
313
+ */
314
+ constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>);
315
+ }
316
+ /**
317
+ * A write kept losing the version race: other writes to the same Owner changed its Overrides every time it re-read
318
+ * them and applied its change again, until `maxRetries` retries were used up. Nothing was stored for this write. It
319
+ * is safe to try the whole write again later.
320
+ */
321
+ declare class SettingsConflictError extends Error {
322
+ /** The Owner whose Overrides couldn't be written. */
323
+ readonly owner: Owner;
324
+ /**
325
+ * @param owner - The Owner whose Overrides couldn't be written.
326
+ * @param retries - How many times the write re-read and applied its change again.
327
+ */
328
+ constructor(owner: Owner, retries: number);
329
+ }
330
+ /**
331
+ * A Scope can't be used as defined: its name isn't in `definitions`, or its defaults, or an Owner's Effective
332
+ * settings, fail its schema. This is a bug in the app's Settings definitions, not in the input, so it isn't meant to
333
+ * be shown to a user.
334
+ */
335
+ declare class SettingsDefinitionError extends Error {
336
+ /** The Scope name the error is about. */
337
+ readonly scope: string;
338
+ /** The Standard Schema issues, when the error is a failed validation. */
339
+ readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
340
+ /**
341
+ * @param message - The error message.
342
+ * @param scope - The Scope name.
343
+ * @param issues - The Standard Schema issues, if any.
344
+ */
345
+ constructor(message: string, scope: string, issues?: ReadonlyArray<StandardSchemaV1.Issue>);
346
+ }
347
+
348
+ export { MemoryStore, Owner, type Path, type PathValue, type ScopedSettings, SettingsConflictError, type SettingsDefinition, SettingsDefinitionError, type SettingsOptions, SettingsStore, SettingsValidationError, StoredOverrides, createSettings, defineSettings };