diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 680889f..3351e70 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -25,7 +25,7 @@ on: - "**/*.md" - ".github/**" - "!.github/workflows/codeql.yml" - + pull_request: paths-ignore: - "**/*.sh" @@ -63,11 +63,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -81,7 +81,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -94,6 +94,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index b0dedc4..0d4a013 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -15,6 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Dependency Review' - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ae45cd2..a022495 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,29 +8,60 @@ on: jobs: publish: runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + # PUBLICANDO EM NPM - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '>=16.8.0' + node-version: '>=20' + registry-url: https://registry.npmjs.org/ + scope: "@heliomarpm" + + - name: Install Dependencies + run: npm ci + + - name: Test & Build + run: | + npm run test + npm run build + + - name: Publish NPM Package + run: npm publish --access=public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - run: npm ci - - run: npm run build - - name: Publish Lib - uses: JS-DevTools/npm-publish@v2 + # PUBLICANDO EM GITHUB PACKAGE + - name: Setup Node GPR + uses: actions/setup-node@v4 with: - token: ${{ secrets.NPM_TOKEN }} - strategy: all + node-version: '>=20' + registry-url: https://npm.pkg.github.com/ + scope: "@heliomarpm" + + - name: Install Dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Publish GPR + run: npm publish --access=public + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - # - uses: actions/setup-node@v3 - # with: - # node-version: '>=18' - # registry-url: https://registry.npmjs.org/ + # # ISSO PUBLICOU NO NPM + # - name: Publish Lib + # uses: JS-DevTools/npm-publish@v3 + # with: + # token: ${{ secrets.NPM_TOKEN }} + # strategy: all - # - run: npm publish - # env: - # NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/package.json b/package.json index c97c54d..5e203dd 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,11 @@ "description": "A simple and robust KeyValues Storage's library", "main": "dist/index.js", "types": "dist/index.d.ts", - "author": "Heliomar P. Marques", + "author": { + "name": "Heliomar P. Marques", + "email": "heliomarpm@proton.me", + "url": "https://navto.me/heliomarpm" + }, "license": "MIT", "bugs": { "url": "https://github.com/heliomarpm/keyvalues-storage/issues" @@ -33,8 +37,8 @@ "storage" ], "scripts": { - "build": "tsc", - "test": "jest --detectOpenHandles", + "build": "tsc -p tsconfig.prod.json", + "test": "jest", "test:c": "jest --coverage", "pub:release": "node ./cmd/modules/release/index.js" }, @@ -43,13 +47,15 @@ "write-file-atomic": "^5.0.1" }, "devDependencies": { - "@types/jest": "^29.5.3", - "@types/write-file-atomic": "^4.0.0", - "jest": "^29.6.3", - "ts-jest": "^29.1.1" + "@types/jest": "^29.5.12", + "@types/write-file-atomic": "^4.0.3", + "jest": "^29.7.0", + "open": "^10.1.0", + "semver": "^7.6.0", + "ts-jest": "^29.1.2" }, "peerDependencies": { - "@types/lodash": "^4.14.194", - "@types/write-file-atomic": "^4.0.0" + "@types/lodash": "^4.17.1", + "@types/write-file-atomic": "^4.0.3" } -} \ No newline at end of file +} diff --git a/src/internal/JsonFileHelper.ts b/src/internal/JsonFileHelper.ts index c7f177c..d4dc4a0 100644 --- a/src/internal/JsonFileHelper.ts +++ b/src/internal/JsonFileHelper.ts @@ -8,7 +8,6 @@ export const DEFAULT_DIR_NAME = 'localdb'; export const DEFAULT_FILE_NAME = 'keyvalues.json'; export class JsonFileHelper { - options: Options; constructor(options: Options) { @@ -24,7 +23,7 @@ export class JsonFileHelper { * @internal */ private getJsonDirPath(): string { - let dir = (this.options.dir ?? path.resolve(DEFAULT_DIR_NAME)).trim(); + const dir = (this.options.dir ?? path.resolve(DEFAULT_DIR_NAME)).trim(); return dir === '' ? './' : dir; } @@ -54,7 +53,7 @@ export class JsonFileHelper { const filePath = this.getJsonFilePath(); return new Promise((resolve, reject) => { - fs.stat(filePath, (err: any) => { + fs.stat(filePath, (err) => { if (err) { if (err.code === 'ENOENT') { this.saveKeyValues({}).then(resolve, reject); @@ -79,7 +78,8 @@ export class JsonFileHelper { try { fs.statSync(filePath); - } catch (err: any) { + } catch (ex) { + const err = ex as NodeJS.ErrnoException; if (err.code === 'ENOENT') { this.saveKeyValuesSync({}); } else { @@ -89,7 +89,7 @@ export class JsonFileHelper { } /** - * Ensures that the keyvalues directory exists. If it does + * Ensures that the KeyValues directory exists. If it does * not exist, then it is created. * * @returns A promise which resolves when the keyvalues dir exists. @@ -99,7 +99,7 @@ export class JsonFileHelper { const dirPath = this.getJsonDirPath(); return new Promise((resolve, reject) => { - fs.stat(dirPath, (err: any) => { + fs.stat(dirPath, (err) => { if (err) { if (err.code === 'ENOENT') { fs.mkdir(dirPath, { recursive: true }, (error: any) => { @@ -117,9 +117,10 @@ export class JsonFileHelper { } /** - * Ensures that the keyvalues directory exists. If it does - * not exist, then it is created. + * Ensures that the KeyValues directory exists synchronously. If it does not exist, + * then it is created. * + * @returns {void} * @internal */ private ensureJsonDirSync(): void { @@ -127,7 +128,9 @@ export class JsonFileHelper { try { fs.statSync(dirPath); - } catch (err: any) { + } catch (ex) { + const err = ex as NodeJS.ErrnoException; + if (err.code === 'ENOENT') { fs.mkdirSync(dirPath, { recursive: true }); // mkdirp.sync(dirPath); @@ -138,10 +141,11 @@ export class JsonFileHelper { } /** - * First ensures that the keyvalues file exists then loads - * the keyvalues from the disk. + * Asynchronously loads key-value pairs from a JSON file. First ensures that the file exists, + * then reads the file and parses its contents into a JavaScript object. * - * @returns A promise which resolves with the keyvalues object. + * @template T - The type of the key-value pairs. + * @return {Promise} A promise that resolves with the key-value pairs. * @internal */ public async loadKeyValues(): Promise { @@ -149,15 +153,15 @@ export class JsonFileHelper { const filePath = this.getJsonFilePath(); return await new Promise((resolve, reject) => { - fs.readFile(filePath, 'utf-8', (err: any, data: string | any[]) => { + fs.readFile(filePath, 'utf-8', (err, data: string | any[]) => { if (err) { reject(err); } else { try { // resolve(JSON.parse(data.length ? data : "{}")); - resolve(JSON.parse(data ? (Array.isArray(data) ? data.join('') : data) : "{}")); - } catch (err_1) { - reject(err_1); + resolve(JSON.parse(data ? (Array.isArray(data) ? data.join('') : data) : '{}')); + } catch (error) { + reject(error); } } }); @@ -165,10 +169,10 @@ export class JsonFileHelper { } /** - * First ensures that the keyvalues file exists then loads - * the keyvalues from the disk. + * Loads the key-value pairs synchronously from the JSON file. * - * @returns The keyvalues object. + * @template T - The type of the key-value pairs. + * @returns {T} - The loaded key-value pairs. * @internal */ public loadKeyValuesSync(): T { @@ -176,14 +180,14 @@ export class JsonFileHelper { const filePath = this.getJsonFilePath(); const data = fs.readFileSync(filePath, 'utf-8'); - return JSON.parse(data.length ? data : "{}"); + return JSON.parse(data.length ? data : '{}'); } /** * Saves the keyvalues to the disk. * - * @param obj The keyvalues object to save. - * @returns A promise which resolves when the keyvalues have been saved. + * @param {T} obj - The keyvalues object to save. + * @return {Promise} A promise that resolves when the keyvalues have been saved. * @internal */ public async saveKeyValues(obj: T): Promise { @@ -195,23 +199,21 @@ export class JsonFileHelper { return await new Promise((resolve, reject) => { if (this.options.atomicSave) { writeFileAtomic(filePath, data, (err: any) => { - return err - ? reject(err) - : resolve(); + return err ? reject(err) : resolve(); }); } else { fs.writeFile(filePath, data, (err_1: any) => { - return err_1 ? reject(err_1) - : resolve(); + return err_1 ? reject(err_1) : resolve(); }); } }); } /** - * Saves the keyvalues to the disk. + * Saves the keyvalues to the disk synchronously. * - * @param obj The keyvalues object to save. + * @param {T} obj - The keyvalues object to save. + * @return {void} This function does not return anything. * @internal */ public saveKeyValuesSync(obj: T): void { @@ -227,5 +229,4 @@ export class JsonFileHelper { fs.writeFileSync(filePath, data); } } - } diff --git a/src/keyvalues.ts b/src/keyvalues.ts index 46bb761..fe12fb3 100644 --- a/src/keyvalues.ts +++ b/src/keyvalues.ts @@ -37,11 +37,18 @@ const defaultOptions: Options = { * await keyValues.unset('color.name'); */ export class KeyValues { + + /** + * @internal + */ private options: Options = { ...defaultOptions, }; - private fnc: JsonFileHelper; + /** + * @internal + */ + private jsonHelper: JsonFileHelper; /** @@ -72,7 +79,7 @@ export class KeyValues { if (options) this.options = { ...this.options, ...options }; - this.fnc = new JsonFileHelper(this.options); + this.jsonHelper = new JsonFileHelper(this.options); } /** @@ -103,7 +110,7 @@ export class KeyValues { * ``` */ file(): string { - return this.fnc.getJsonFilePath(); + return this.jsonHelper.getJsonFilePath(); } /** @@ -164,7 +171,7 @@ export class KeyValues { * ``` */ async has(keyPath: KeyPath): Promise { - const obj = await this.fnc.loadKeyValues(); + const obj = await this.jsonHelper.loadKeyValues(); return _has(obj, keyPath); } @@ -212,7 +219,7 @@ export class KeyValues { * ``` */ hasSync(keyPath: KeyPath): boolean { - const obj = this.fnc.loadKeyValuesSync(); + const obj = this.jsonHelper.loadKeyValuesSync(); return _has(obj, keyPath); } @@ -285,7 +292,7 @@ export class KeyValues { async get(keyPath: KeyPath): Promise; async get(keyPath?: KeyPath): Promise { - const obj = await this.fnc.loadKeyValues(); + const obj = await this.jsonHelper.loadKeyValues(); if (keyPath) { return _get(obj, keyPath); @@ -360,7 +367,7 @@ export class KeyValues { getSync(keyPath: KeyPath): T; getSync(keyPath?: KeyPath): T { - const obj = this.fnc.loadKeyValuesSync(); + const obj = this.jsonHelper.loadKeyValuesSync(); if (keyPath) { return _get(obj, keyPath); @@ -435,14 +442,14 @@ export class KeyValues { if (args.length === 1) { const [value] = args; - return this.fnc.saveKeyValues(value); + return this.jsonHelper.saveKeyValues(value); } else { const [keyPath, value] = args; - const obj = await this.fnc.loadKeyValues(); + const obj = await this.jsonHelper.loadKeyValues(); _set(obj as object, keyPath, value); - return this.fnc.saveKeyValues(obj); + return this.jsonHelper.saveKeyValues(obj); } } @@ -508,14 +515,14 @@ export class KeyValues { if (args.length === 1) { const [value] = args; - this.fnc.saveKeyValuesSync(value); + this.jsonHelper.saveKeyValuesSync(value); } else { const [keyPath, value] = args; - const obj = this.fnc.loadKeyValuesSync(); + const obj = this.jsonHelper.loadKeyValuesSync(); _set(obj as object, keyPath, value); - this.fnc.saveKeyValuesSync(obj); + this.jsonHelper.saveKeyValuesSync(obj); } } @@ -577,14 +584,14 @@ export class KeyValues { async unset(keyPath?: KeyPath): Promise { if (keyPath) { - const obj = await this.fnc.loadKeyValues(); + const obj = await this.jsonHelper.loadKeyValues(); _unset(obj, keyPath); - return this.fnc.saveKeyValues(obj); + return this.jsonHelper.saveKeyValues(obj); } else { // Unset all keyValues by saving empty object. - return this.fnc.saveKeyValues({}); + return this.jsonHelper.saveKeyValues({}); } } @@ -642,14 +649,14 @@ export class KeyValues { unsetSync(keyPath?: KeyPath): void { if (keyPath) { - const obj = this.fnc.loadKeyValuesSync(); + const obj = this.jsonHelper.loadKeyValuesSync(); _unset(obj, keyPath); - this.fnc.saveKeyValuesSync(obj); + this.jsonHelper.saveKeyValuesSync(obj); } else { // Unset all keyValues by saving empty object. - this.fnc.saveKeyValuesSync({}); + this.jsonHelper.saveKeyValuesSync({}); } } } diff --git a/test/index.test.ts b/test/index.test.ts index fa6b6d6..4abed27 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,12 +1,6 @@ +import fs from 'node:fs'; import { KeyValues } from '../src'; -const options: Options = { - atomicSave: false, - dir: 'localdb/data', - fileName: 'settings.json', - prettify: true, - numSpaces: 4, -}; const defaultOptions: Options = { atomicSave: true, dir: 'localdb', @@ -15,7 +9,13 @@ const defaultOptions: Options = { numSpaces: 2, }; -const kvs = new KeyValues(options); +const options: Options = { + atomicSave: false, + dir: 'localdb/data', + fileName: 'settings.json', + prettify: true, + numSpaces: 4, +}; const complex = { name: 'complex', @@ -47,9 +47,30 @@ const persons: IPerson[] = [ // console.log(JSON.stringify(p.toString())); // console.log('age', t.age); +const deleteFiles = () => { + //delete json file + const kvs = new KeyValues(options); + if (fs.existsSync(kvs.file())) { + fs.unlinkSync(kvs.file()); + } + + const kvsDefault = new KeyValues(defaultOptions); + if (fs.existsSync(kvsDefault.file())) { + fs.unlinkSync(kvsDefault.file()); + } +}; + describe('KeyValues Default Test', () => { - it('healthCheck', () => { - expect(1).toBe(1); + let kvs: KeyValues; + + beforeAll(() => { + deleteFiles(); + + kvs = new KeyValues(options); + }); + + afterAll(() => { + // deleteFiles(); }); // test('test set symbol', () => { @@ -75,6 +96,7 @@ describe('KeyValues Default Test', () => { await kvs.set({ propAsync: true }); expect(await kvs.get('propAsync')).toBeTruthy(); }); + it('test set/get property', () => { kvs.setSync({ prop: false }); expect(kvs.getSync('prop')).toBeFalsy(); @@ -146,6 +168,7 @@ describe('KeyValues Default Test', () => { const r = kvs.getSync('person'); expect(r.name).toEqual('John Doe'); }); + it('test set/get of array map object', () => { const mapPerson = persons.map((person) => ({ ...person })); kvs.setSync('persons', mapPerson); @@ -200,6 +223,7 @@ describe('KeyValues Default Test', () => { const r = await kvs.has('string'); expect(r).toBeTruthy(); }); + it('test unset propertie async', async () => { await kvs.unset('string'); const r = await kvs.has('string'); @@ -209,6 +233,7 @@ describe('KeyValues Default Test', () => { it('test has propertie', () => { expect(kvs.hasSync('complex')).toBeTruthy(); }); + it('test unset propertie', () => { kvs.unsetSync('complex'); expect(kvs.hasSync('complex')).toBeFalsy(); @@ -221,10 +246,12 @@ describe('KeyValues Default Test', () => { it('test unset undefined propertie', () => { expect(kvs.unsetSync('undefined')).toBeFalsy(); }); + it('test unset/get ', () => { kvs.unsetSync('boolean'); expect(kvs.getSync('boolean')).toBeUndefined(); }); + it('test unsetAll/get all async', async () => { await kvs.unset(); expect(kvs.getSync()).toEqual({}); diff --git a/test/jsonFile.test.ts b/test/jsonFile.test.ts index 0b108a5..531eaf3 100644 --- a/test/jsonFile.test.ts +++ b/test/jsonFile.test.ts @@ -1,17 +1,14 @@ -// Generated by CodiumAI import fs from 'node:fs'; import { KeyValues } from '../src/keyvalues'; import { JsonFileHelper } from '../src/internal/JsonFileHelper'; describe('KeyValues', () => { - it('healthCheck', () => { - expect(1).toBe(1); - }); - - afterAll(() => { + beforeAll(() => { //delete json file const kvs = new KeyValues(); - fs.unlinkSync(kvs.file()); + if (fs.existsSync(kvs.file())) { + fs.unlinkSync(kvs.file()); + } }); it('should return the path to the keyvalues file with default options', () => { @@ -123,7 +120,6 @@ describe('KeyValues', () => { // Assert expect(result).toEqual({}); - fs.unlinkSync(jsonFileHelper.getJsonFilePath()); }); }); diff --git a/tsconfig.json b/tsconfig.json index 921fa28..9781695 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "strict": true, "useDefineForClassFields": true, - "forceConsistentCasingInFileNames": true, + "forceConsistentCasingInFileNames": true, "target": "ES6", "module": "CommonJS", "outDir": "./dist", diff --git a/tsconfig.prod.json b/tsconfig.prod.json new file mode 100644 index 0000000..6865f33 --- /dev/null +++ b/tsconfig.prod.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": false, + "declarationMap": false + } +}