From 3704d198e02051fc3f70c4756dbb3f8e3cf020b5 Mon Sep 17 00:00:00 2001 From: Batuhan Wilhelm Date: Sat, 30 Dec 2023 02:04:17 +0300 Subject: [PATCH] feat: v1 updates (#32) * refactor: minor changes to specs * docs: add jsdoc comments * fix: make set store private * 1.0.0 * chore: remove redundant @types/rimraf package * chore: update README --- README.md | 2 +- package-lock.json | 15 +------ package.json | 5 +-- spec/index.spec.ts | 46 ++++++++++---------- src/index.ts | 102 ++++++++++++++++++++++++++++++++++++++++++++- src/types.ts | 1 + 6 files changed, 131 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index ba4e770..e52e74f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Maintainability](https://api.codeclimate.com/v1/badges/4315aa36678fe4181b77/maintainability)](https://codeclimate.com/github/BatuhanW/haf/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/4315aa36678fe4181b77/test_coverage)](https://codeclimate.com/github/BatuhanW/haf/test_coverage) -Haf is a fully typed 🔒, cross-platform, persistent 💾 config ⚙️ solution for your NodeJS projects with a great developer experience! +Haf is a fully typed 🔒, cross-platform, persistent 💾 JSON storage ⚙️ solution for your NodeJS projects with a great developer experience! - ✏️ Auto-completed dot-notation suggestions as you type when you try to get()/set()/delete()/reset() data from the store. - ✅ The type of the value you get() from the store is correctly inferred. So you always know what you'll get(). diff --git a/package-lock.json b/package-lock.json index b1df68a..ead212c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@batuhanw/haf", - "version": "0.2.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@batuhanw/haf", - "version": "0.2.0", + "version": "1.0.0", "license": "MIT", "dependencies": { "fs-extra": "^11.2.0", @@ -16,7 +16,6 @@ "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.11", "@types/node": "^18.19.3", - "@types/rimraf": "^4.0.5", "@typescript-eslint/eslint-plugin": "^6.16.0", "@typescript-eslint/parser": "^6.16.0", "eslint": "^8.56.0", @@ -1523,16 +1522,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/rimraf": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-4.0.5.tgz", - "integrity": "sha512-DTCZoIQotB2SUJnYgrEx43cQIUYOlNZz0AZPbKU4PSLYTUdML5Gox0++z4F9kQocxStrCmRNhi4x5x/UlwtKUA==", - "deprecated": "This is a stub types definition. rimraf provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "rimraf": "*" - } - }, "node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", diff --git a/package.json b/package.json index 831a266..3b8d5af 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "@batuhanw/haf", - "version": "0.2.0", + "version": "1.0.0", "engines": { "node": ">=16" }, - "description": "Fully typed, modern, cross-platform persistent config solution for NodeJS projects", + "description": "Fully typed, modern, cross-platform persistent JSON storage solution for NodeJS projects.", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ @@ -46,7 +46,6 @@ "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.11", "@types/node": "^18.19.3", - "@types/rimraf": "^4.0.5", "@typescript-eslint/eslint-plugin": "^6.16.0", "@typescript-eslint/parser": "^6.16.0", "eslint": "^8.56.0", diff --git a/spec/index.spec.ts b/spec/index.spec.ts index c9c8aac..27fbf41 100644 --- a/spec/index.spec.ts +++ b/spec/index.spec.ts @@ -246,9 +246,9 @@ describe('Haf', () => { }); it('appends string', () => { - haf.append('favoriteToys', 'socks'); + haf.append('favoriteToys', 'socks', 'ball'); - expect(haf.get('favoriteToys')).toEqual(['toilet paper', 'socks']); + expect(haf.get('favoriteToys')).toEqual(['toilet paper', 'socks', 'ball']); }); it('appends number', () => { @@ -284,35 +284,37 @@ describe('Haf', () => { }); describe('delete()', () => { + const defaultSchema = { + age: 2, + appearance: { + eyeColor: 'brown', + hairColor: { + primary: 'white', + otherColors: ['pink'], + }, + birthMarks: ['head'], + }, + favoriteToys: ['socks', 'toilet_paper'], + hasPuppies: false, + luckyNumbers: [4, 2], + name: 'Pop', + vaccines: [ + { name: 'rabies', date: '2020-01-22', next: { date: '2020-07-22' } }, + { name: 'parasite', date: '2020-01-22' }, + ], + }; + beforeEach(() => { haf = new Haf({ name: 'pop', - defaultSchema: { - age: 2, - appearance: { - eyeColor: 'brown', - hairColor: { - primary: 'white', - otherColors: ['pink'], - }, - birthMarks: ['head'], - }, - favoriteToys: ['socks', 'toilet_paper'], - hasPuppies: false, - luckyNumbers: [4, 2], - name: 'Pop', - vaccines: [ - { name: 'rabies', date: '2020-01-22', next: { date: '2020-07-22' } }, - { name: 'parasite', date: '2020-01-22' }, - ], - }, + defaultSchema, }); }); it('undefined', () => { haf.delete('sterilizedAt'); - expect(haf.get('sterilizedAt')).toBeUndefined(); + expect(haf.store).toEqual({ ...defaultSchema, sterilizedAt: undefined }); }); }); diff --git a/src/index.ts b/src/index.ts index 59510ec..5a1dd46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,9 +4,34 @@ import { getStorePath } from './get-store-path'; import type { FlattenedWithDotNotation, OptionalKeysOf, StringKeysOf, ArrayKeysOf } from './types'; interface HafConfig { + /** + * @description name of the store file. + * @required + */ name: string; + /** + * @description extension of the store file. + * @optional + * @default haf + * @file /path/to/store/storeName.**haf** + */ extension?: string; + /** + * @description default JSON object to use when initializing the store. + * @optional + * @default {} + */ defaultSchema?: Partial; + /** + * @description directory for the store file. + * @default checks the environment variables in the following order: + * - process.env.CONFIG_DIR // unix + * - process.env.XDG_CONFIG_HOME // unix + * - process.env.LOCALAPPDATA // windows + * fallback: + * - os.homedir() + '/.config' + * @optional + */ storeDir?: string; } @@ -31,18 +56,63 @@ class Haf> { return readJsonSync(this.storePath); } - set store(schema: Schema | Partial) { + private set store(schema: Schema | Partial) { writeJsonSync(this.storePath, schema); } + /** + * @description returns the value at the provided path. + * @param path + * @example + * ```typescript + * const haf = new Haf({ + * name: 'dog', + * defaultSchema: { + * name: 'Popita' + * } + * }) + * + * haf.get('name') // Popita + * ``` + */ get>(path: Path): FlattenedSchema[Path] { return this._get(path); } + /** + * @description sets the value to the provided path. + * @param path + * @param value + * @example + * ```typescript + * const haf = new Haf({ + * name: 'dog', + * }) + * + * haf.set('name', 'Popita') + * ``` + */ set>(path: Path, value: FlattenedSchema[Path]): void { this._set(path, value); } + /** + * @description appends the provided values to the array at the provided path. + * @param path + * @param ...values + * @example + * ```typescript + * const haf = new Haf({ + * name: 'dog', + * }) + * + * haf.set('favoriteToys', ['ball', 'bone']) + * + * haf.append('favoriteToys', 'socks', 'toilet paper') + * + * haf.get('favoriteToys') // ['ball', 'bone', 'socks', 'toilet paper'] + * ``` + */ append< Path extends ArrayKeysOf, Values extends Extract, @@ -54,10 +124,37 @@ class Haf> { this._set(path, existingValues); } + /** + * @description sets the value of the provided path to undefined. + */ delete(path: OptionalKeysOf): void { this._set(path, undefined); } + /** + * @description resets the store to defaultSchema values. Sets fields to undefined if not provided in defaultSchema. + * @param path optional path to reset. if not provided, the entire store will be reset. + * @example + * ```typescript + * const haf = new Haf({ + * name: 'dog', + * defaultSchema: { + * name: 'pop', + * }); + * + * haf.set('name', 'pup'); + * + * haf.reset('name'); + * + * haf.get('name'); // pop + * + * haf.set('age', 3) + * + * haf.reset('age') + * + * haf.get('age') // undefined + * ``` + */ reset(path?: StringKeysOf): void { if (typeof path === 'undefined') { this.store = this.defaultSchema; @@ -70,6 +167,9 @@ class Haf> { this._set(path, defaultValue); } + /** + * @description removes the store file from the file system. + */ nuke(): void { removeSync(this.storePath); } diff --git a/src/types.ts b/src/types.ts index 742b082..a6b4d7c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,6 +24,7 @@ export type FlattenedWithDotNotation = [K in string & keyof Schema as AddPrefix]: Schema[K]; } /* then, for each sub-object, recurse */ & IntersectValuesOf<{ // see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types + // Since Array type also satisfies `object`, excluding it before starting recursion for `object` type. [K in string & keyof Schema as AddPrefix]: Schema[K] extends Array ? never : Schema[K] extends object