Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added dot notation along with appropriate types #3

Merged
merged 4 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { has, get, set } from './settings//getter-setter';
export { get, set } from './settings//getter-setter';
export { getSettings as getAll } from './fs/load-save';
export { SettingsManager } from './settings-manager/settings-manager';

Expand Down
50 changes: 21 additions & 29 deletions src/settings-manager/settings-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { ConfigOptions } from '../config/config';
import { STATUS } from '../fs/ensure-settings-file';

import { getSettings, saveSettings } from '../fs/load-save';
import { has, get, set } from '../settings/getter-setter';
import { get, set, has } from '../settings/getter-setter';
import { getDotNotation, setDotNotation } from '../utils/dot-notation';
import type { Path, PathValue } from '../types/dot-notation';

export class SettingsManager<SettingsSchema extends {} = any> {
/**
Expand Down Expand Up @@ -54,20 +56,19 @@ export class SettingsManager<SettingsSchema extends {} = any> {
* Checks whether a key exists in the settings cache.
* @param key The key for the setting
*/
hasCache(key: string | number | symbol): boolean {
return key in this.settings;
hasCache<K extends Path<SettingsSchema>>(key: K): boolean {
return getDotNotation(this.settings, key) !== undefined;
}

/**
* Sets the value of a setting from the cache.
* @param key The key for the setting
* @returns The value of the setting
*/
getCache<K extends keyof SettingsSchema = keyof SettingsSchema>(key: K): SettingsSchema[K] {
if (this.hasCache(key)) {
return this.settings[key];
}
else throw 'Error: key does not exist';
getCache<K extends Path<SettingsSchema>>(key: K): PathValue<SettingsSchema, K> {
if (this.hasCache(key)) throw 'Error: key does not exist';

return getDotNotation<SettingsSchema, K>(this.settings, key);
}

/**
Expand All @@ -76,35 +77,26 @@ export class SettingsManager<SettingsSchema extends {} = any> {
* @param value The new value for the setting
* @returns The entire settings object
*/
setCache<K extends keyof SettingsSchema = keyof SettingsSchema>(key: K, value: SettingsSchema[K]): SettingsSchema[K] {
this.settings[key] = value;
setCache<K extends Path<SettingsSchema>, V extends PathValue<SettingsSchema, K>>(key: K, value: V): V {
if (this.hasCache(key)) throw 'Error: key does not exist';

return value;
}
setDotNotation<SettingsSchema, K>(this.settings, key, value);

/**
* Checks whether a key exists in the settings directly from the storage.
* @param key The key for the setting
*/
async has(key: string | number | symbol): Promise<boolean> {
return await has<SettingsSchema>(key, this.options);
return value;
}

/**
* Gets the value of a setting directly from the storage. Also updates cache.
* @param key The key for the setting
* @returns The value of the setting
*/
async get<K extends keyof SettingsSchema = keyof SettingsSchema>(key: K): Promise<SettingsSchema[K]> {
if (await this.has(key)) {
const value = await get<SettingsSchema, K>(key, this.options);
async get<K extends Path<SettingsSchema>>(key: K): Promise<PathValue<SettingsSchema, K>> {
const value = await get<SettingsSchema, K>(key, this.options);

// to also update cache
this.setCache(key, value);
// to also update cache
this.setCache(key, value);

return value;
}
else throw 'Error: key does not exist';
return value;
}

/**
Expand All @@ -113,11 +105,11 @@ export class SettingsManager<SettingsSchema extends {} = any> {
* @param value The new value for the setting
* @returns The entire settings object
*/
async set<K extends keyof SettingsSchema = keyof SettingsSchema>(key: K, value: SettingsSchema[K]): Promise<SettingsSchema> {
async set<K extends Path<SettingsSchema>, V extends PathValue<SettingsSchema, K>>(key: K, value: V): Promise<SettingsSchema> {
// to also update cache
this.setCache(key, value);

return await set<SettingsSchema, K>(key, value, this.options);
return await set<SettingsSchema, K, V>(key, value, this.options);
}

/**
Expand All @@ -129,4 +121,4 @@ export class SettingsManager<SettingsSchema extends {} = any> {

return this.settings;
}
}
}
32 changes: 18 additions & 14 deletions src/settings/getter-setter.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import { ConfigOptions } from '../config/config';
import { getSettings, saveSettings } from '../fs/load-save';
import { getDotNotation, setDotNotation } from '../utils/dot-notation';
import type { Path, PathValue } from '../types/dot-notation';

/**
* Checks whether a key exists in the settings.
* @param key The key for the setting
*/
export async function has
<SettingsSchema extends {} = any>
(key: string | number | symbol, options: ConfigOptions = {}): Promise<boolean>
{
return (key in (await getSettings<SettingsSchema>(options)).settings);
<SettingsSchema, K extends Path<SettingsSchema>>
(key: K, options: ConfigOptions = {}): Promise<boolean> {
const settings = (await getSettings<SettingsSchema>(options)).settings;
const value = getDotNotation(settings, key);
return value !== undefined;
}

/**
* Get the value of a particular setting.
* @param key The key for the setting
* @returns The value
* @returns The value of the setting
*/
export async function get
<SettingsSchema extends {} = any, K extends keyof SettingsSchema = keyof SettingsSchema>
(key: K, options: ConfigOptions = {}): Promise<SettingsSchema[K]>
{
if (await has<SettingsSchema>(key)) return (await getSettings<SettingsSchema>(options)).settings[key];
else throw 'Error: key does not exist.'
<SettingsSchema, K extends Path<SettingsSchema>>
(key: K, options: ConfigOptions = {}): Promise<PathValue<SettingsSchema, K>> {
if (await has<SettingsSchema, K>(key)) throw 'Error: key does not exist';
const settings = (await getSettings<SettingsSchema>(options)).settings;
return getDotNotation<SettingsSchema, K>(settings, key);
}

/**
Expand All @@ -32,12 +35,13 @@ export async function get
* @returns The entire settings object
*/
export async function set
<SettingsSchema extends {} = any, K extends keyof SettingsSchema = keyof SettingsSchema>
(key: K, value: SettingsSchema[K], options: ConfigOptions = {}): Promise<SettingsSchema>
{
<SettingsSchema, K extends Path<SettingsSchema>, V extends PathValue<SettingsSchema, K>>
(key: K, value: V, options: ConfigOptions = {}): Promise<SettingsSchema> {
if (await has<SettingsSchema, K>(key)) throw 'Error: key does not exist';

const settings = await getSettings<SettingsSchema>(options);
setDotNotation<SettingsSchema, K>(settings.settings, key, value);

settings.settings[key] = value;
await saveSettings<SettingsSchema>(settings.settings, settings.path, options);

return settings.settings;
Expand Down
21 changes: 21 additions & 0 deletions src/types/dot-notation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type PathImpl<T, K extends keyof T> =
K extends string
? T[K] extends Record<string, any>
? T[K] extends ArrayLike<any>
? K | `${K}.${PathImpl<T[K], Exclude<keyof T[K], keyof any[]>>}`
: K | `${K}.${PathImpl<T[K], keyof T[K]>}`
: K
: never;

export type Path<T> = PathImpl<T, keyof T> | keyof T;

export type PathValue<T, P extends Path<T>> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Rest extends Path<T[K]>
? PathValue<T[K], Rest>
: never
: never
: P extends keyof T
? T[P]
: never;
48 changes: 48 additions & 0 deletions src/utils/dot-notation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Path, PathValue } from '../types/dot-notation';

/**
* @internal
*/
export function getDotNotation<SettingsSchema, K extends Path<SettingsSchema>>(obj: SettingsSchema, path: K): PathValue<SettingsSchema, K> | undefined {
if (typeof path !== 'string') throw 'Error: path must be a string';

const keys = path.split('.');

let current: any = obj;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];

if (current[key] === undefined) {
return undefined;
} else {
current = current[key];
}
}

return current;
}

/**
* @internal
*/
export function setDotNotation<SettingsSchema, K extends Path<SettingsSchema>>(obj: SettingsSchema, path: K, value: PathValue<SettingsSchema, K>): PathValue<SettingsSchema, K> | undefined {
if (typeof path !== 'string') throw 'Error: path must be a string';

const keys = path.split('.');

let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];

if (current[key] === undefined) {
return undefined;
}

current = current[key];
}

if (current[keys[keys.length - 1]] === undefined) return undefined;

current[keys[keys.length - 1]] = value;
return value;
}