rengandb 1.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Talha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Readme.md ADDED
@@ -0,0 +1,35 @@
1
+ # rengandb
2
+
3
+ A **safe**, **performant**, and **file-locking protected** JSON/YAML database module for Node.js.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npm install rengandb
9
+ ```
10
+
11
+ ```javascript
12
+ import Database from 'rengandb';
13
+
14
+ const db = await Database.init('data.json', { useLodash: true });
15
+
16
+ // Write data
17
+ await db.set('user.name', 'Rengan');
18
+
19
+ // Read data
20
+ console.log(db.get('user.name')); // "Rengan"
21
+
22
+ ```
23
+
24
+ ## Why use it?
25
+
26
+ * **Safe:** Prevents data corruption using `proper-lockfile`, even when multiple processes try to write to the file simultaneously.
27
+ * **Smart:** Automatically refreshes data in RAM using `chokidar` when the file is modified externally.
28
+ * **Lightweight:** Optional `lodash` support; use it only if you need deep access (e.g., `user.settings.theme`).
29
+
30
+ ## License
31
+
32
+ This project is licensed under the MIT License.
33
+ See the [LICENSE](LICENSE) file for details.
34
+
35
+ > **Note:** This is a hobby project. Please use it responsibly in production environments.
@@ -0,0 +1,57 @@
1
+ interface IEngine {
2
+ get<T>(key: string): T | undefined;
3
+ set<T>(key: string, value: T): void;
4
+ has(key: string): boolean;
5
+ delete(key: string): void;
6
+ }
7
+ interface IProvider {
8
+ stringify(data: any): any;
9
+ parse(data: any): any;
10
+ }
11
+ interface IFileSys {
12
+ isExists(path: string): Promise<boolean>;
13
+ writeFile(path: string, data: any): void;
14
+ readFile(path: string): Promise<string>;
15
+ watch(path: string): any;
16
+ stopWatcher(): Promise<void>;
17
+ }
18
+ interface IDatabaseSettings {
19
+ useLodash?: boolean;
20
+ customFileSystem?: IFileSys;
21
+ }
22
+ declare class Database {
23
+ file: string;
24
+ fileType: string;
25
+ initialized: boolean;
26
+ settings: IDatabaseSettings;
27
+ engine: IEngine;
28
+ provider: IProvider;
29
+ filesystem: IFileSys;
30
+ watcher: any;
31
+ data: any;
32
+ private writeQueue;
33
+ private isWriting;
34
+ constructor(file: string, settings?: IDatabaseSettings);
35
+ static init(...parameters: ConstructorParameters<typeof Database>): Promise<Database>;
36
+ _lodash(): IEngine;
37
+ _native(): IEngine;
38
+ _json(): IProvider;
39
+ _yaml(): IProvider;
40
+ _fs(): IFileSys;
41
+ setup(): Promise<boolean>;
42
+ startWatcher(): Promise<void>;
43
+ loadData(): Promise<any>;
44
+ write(): Promise<void>;
45
+ error(message: string): Error;
46
+ set(key: string, value: any): Promise<void>;
47
+ delete(key: string): Promise<void>;
48
+ math(key: string, value: any, func: (found: number, value: number) => number): Promise<void>;
49
+ push(key: string, value: any): Promise<void>;
50
+ length(key: string): number | undefined;
51
+ has(key: string): boolean;
52
+ clear(really: boolean): Promise<void>;
53
+ fetch(key: string): {} | null;
54
+ get(key: string): {} | null;
55
+ fetchAll(): any;
56
+ }
57
+ export default Database;
package/build/main.js ADDED
@@ -0,0 +1,290 @@
1
+ import fsPromises from 'fs/promises';
2
+ import path from 'path';
3
+ import YAML from 'yaml';
4
+ import chokidar from 'chokidar';
5
+ import lockfile from 'proper-lockfile';
6
+ let lodash;
7
+ const supportedFileTypes = [
8
+ "json",
9
+ "yaml",
10
+ "yml"
11
+ ];
12
+ class Database {
13
+ file;
14
+ fileType;
15
+ initialized;
16
+ settings;
17
+ engine;
18
+ provider;
19
+ filesystem;
20
+ watcher;
21
+ data;
22
+ writeQueue;
23
+ isWriting;
24
+ constructor(file, settings = {}) {
25
+ this.initialized = false;
26
+ this.file = file;
27
+ this.fileType = path.extname(file).toLowerCase();
28
+ if (!supportedFileTypes.includes(this.fileType.replace(/^\./, ''))) {
29
+ this.initialized = false;
30
+ throw this.error("Unsupported file type!");
31
+ }
32
+ this.settings = {
33
+ useLodash: false,
34
+ customFileSystem: undefined,
35
+ ...settings
36
+ };
37
+ this.engine = this._native();
38
+ this.provider = this._json();
39
+ this.filesystem = this.settings.customFileSystem || this._fs();
40
+ this.watcher = null;
41
+ this.writeQueue = Promise.resolve();
42
+ this.isWriting = false;
43
+ }
44
+ static async init(...parameters) {
45
+ const instance = new Database(...parameters);
46
+ await instance.setup();
47
+ instance.initialized = true;
48
+ instance.data = await instance.loadData();
49
+ await instance.startWatcher();
50
+ return instance;
51
+ }
52
+ // Engines (is it lodash or not)
53
+ _lodash() {
54
+ return {
55
+ get: (key) => lodash.get(this.data, key),
56
+ set: (key, value) => lodash.set(this.data, key, value),
57
+ has: (key) => lodash.has(this.data, key),
58
+ delete: (key) => lodash.unset(this.data, key)
59
+ };
60
+ }
61
+ _native() {
62
+ return {
63
+ get: (key) => this.data[key],
64
+ set: (key, value) => { this.data[key] = value; },
65
+ has: (key) => (key in this.data),
66
+ delete: (key) => { delete this.data[key]; }
67
+ };
68
+ }
69
+ // File Types
70
+ _json() {
71
+ return {
72
+ stringify: (data) => JSON.stringify(data, null, 4),
73
+ parse: (data) => JSON.parse(data)
74
+ };
75
+ }
76
+ _yaml() {
77
+ return {
78
+ stringify: (data) => YAML.stringify(data, null, { indent: 4 }),
79
+ parse: (data) => YAML.parse(data)
80
+ };
81
+ }
82
+ // File Systems
83
+ _fs() {
84
+ return {
85
+ isExists: async (path) => {
86
+ try {
87
+ await fsPromises.access(path);
88
+ return true;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ },
94
+ writeFile: (path, data) => fsPromises.writeFile(path, data),
95
+ readFile: (path) => fsPromises.readFile(path, 'utf8'),
96
+ watch: (path) => {
97
+ const watcher = chokidar.watch(path, {
98
+ ignoreInitial: true,
99
+ awaitWriteFinish: {
100
+ stabilityThreshold: 100,
101
+ pollInterval: 50
102
+ },
103
+ });
104
+ watcher
105
+ .on('change', (path) => {
106
+ if (!this.isWriting) {
107
+ this.loadData().then(data => { this.data = data; }).catch(err => { console.error(this.error("Watcher cannot read file")); });
108
+ }
109
+ })
110
+ .on('unlink', (path) => {
111
+ this.loadData().then(data => { this.data = data; }).catch(err => { console.error(this.error("Watcher cannot read file")); });
112
+ });
113
+ return watcher;
114
+ },
115
+ stopWatcher: async () => { if (this.watcher) {
116
+ await this.watcher.close();
117
+ } }
118
+ };
119
+ }
120
+ async setup() {
121
+ if (this.settings.useLodash) {
122
+ try {
123
+ const [getModule, setModule, hasModule, unsetModule] = await Promise.all([
124
+ // @ts-ignore
125
+ import('lodash/get.js'),
126
+ // @ts-ignore
127
+ import('lodash/set.js'),
128
+ // @ts-ignore
129
+ import('lodash/has.js'),
130
+ // @ts-ignore
131
+ import('lodash/unset.js')
132
+ ]);
133
+ lodash = {
134
+ get: getModule.default || getModule,
135
+ set: setModule.default || setModule,
136
+ has: hasModule.default || hasModule,
137
+ unset: unsetModule.default || unsetModule
138
+ };
139
+ this.engine = this._lodash();
140
+ }
141
+ catch (err) {
142
+ console.error(this.error(`Lodash cannot be loaded: ${err}`));
143
+ this.engine = this._native();
144
+ }
145
+ }
146
+ if (this.fileType === '.yaml' || this.fileType === '.yml') {
147
+ this.provider = this._yaml();
148
+ }
149
+ else if (this.fileType === '.json') {
150
+ this.provider = this._json();
151
+ }
152
+ return true;
153
+ }
154
+ async startWatcher() {
155
+ if (this.watcher) {
156
+ await this.filesystem.stopWatcher();
157
+ }
158
+ this.watcher = await this.filesystem.watch(this.file);
159
+ }
160
+ async loadData() {
161
+ if (!this.initialized)
162
+ throw this.error("Not initialized");
163
+ try {
164
+ if (!await this.filesystem.isExists(this.file)) {
165
+ await this.filesystem.writeFile(this.file, this.provider.stringify({}));
166
+ return {};
167
+ }
168
+ return this.provider.parse(await this.filesystem.readFile(this.file)) || {};
169
+ }
170
+ catch {
171
+ return {};
172
+ }
173
+ }
174
+ async write() {
175
+ if (!this.initialized)
176
+ throw this.error("Not initialized");
177
+ this.writeQueue = this.writeQueue.then(async () => {
178
+ this.isWriting = true;
179
+ let releaseLock;
180
+ try {
181
+ releaseLock = await lockfile.lock(this.file, {
182
+ retries: { retries: 10, minTimeout: 50 },
183
+ stale: 5000,
184
+ });
185
+ const content = this.provider.stringify(this.data);
186
+ await this.filesystem.writeFile(this.file, content);
187
+ await new Promise(resolve => setTimeout(resolve, 150));
188
+ }
189
+ catch (err) {
190
+ throw err;
191
+ }
192
+ finally {
193
+ this.isWriting = false;
194
+ if (typeof releaseLock === 'function') {
195
+ await releaseLock();
196
+ }
197
+ }
198
+ }).catch(err => {
199
+ this.isWriting = false;
200
+ throw this.error(`Writing Error: ${err.message}`);
201
+ });
202
+ await this.writeQueue;
203
+ }
204
+ error(message) {
205
+ return new Error(`Saver.db error: ${message}`);
206
+ }
207
+ async set(key, value) {
208
+ if (!key)
209
+ throw this.error(`Undefined key! - ${key}`);
210
+ if (value === undefined)
211
+ throw this.error(`Undefined value! - ${value}`);
212
+ this.engine.set(key, value);
213
+ await this.write();
214
+ }
215
+ async delete(key) {
216
+ if (!key)
217
+ throw this.error(`Undefined key! - ${key}`);
218
+ if (!this.engine.has(key))
219
+ throw this.error(`${key} not found in database.`);
220
+ this.engine.delete(key);
221
+ await this.write();
222
+ }
223
+ async math(key, value, func) {
224
+ if (!key)
225
+ throw this.error(`Undefined key! - ${key}`);
226
+ if (value === undefined)
227
+ throw this.error(`Undefined value! - ${value}`);
228
+ if (!func)
229
+ throw this.error(`Undefined func!`);
230
+ if (!this.engine.has(key))
231
+ throw this.error(`${key} not found in database.`);
232
+ const found = this.engine.get(key);
233
+ const numValue = Number(value);
234
+ if (Number.isNaN(numValue))
235
+ throw this.error('Value is not number!');
236
+ if (isNaN(found))
237
+ throw this.error('Found data is not number!');
238
+ this.engine.set(key, func(found, numValue));
239
+ await this.write();
240
+ }
241
+ async push(key, value) {
242
+ if (!key)
243
+ throw this.error(`Undefined key! - ${key}`);
244
+ if (value === undefined)
245
+ throw this.error(`Undefined value! - ${value}`);
246
+ let found = this.engine.get(key);
247
+ if (found === undefined) {
248
+ found = [];
249
+ }
250
+ else if (!Array.isArray(found)) {
251
+ throw this.error(`Thats not an array! - ${key}`);
252
+ }
253
+ found.push(value);
254
+ this.engine.set(key, found);
255
+ await this.write();
256
+ }
257
+ length(key) {
258
+ const found = this.engine.get(key);
259
+ if (found === undefined) {
260
+ return undefined;
261
+ }
262
+ else if (!Array.isArray(found)) {
263
+ throw this.error('Thats not an array!');
264
+ }
265
+ return found.length;
266
+ }
267
+ has(key) {
268
+ if (!key)
269
+ throw this.error(`Undefined key! - ${key}`);
270
+ return this.engine.has(key);
271
+ }
272
+ async clear(really) {
273
+ if (really) {
274
+ this.data = {};
275
+ await this.write();
276
+ }
277
+ }
278
+ fetch(key) {
279
+ if (!key)
280
+ throw this.error(`Undefined key! - ${key}`);
281
+ return this.engine.get(key) ?? null;
282
+ }
283
+ get(key) {
284
+ return this.fetch(key);
285
+ }
286
+ fetchAll() {
287
+ return this.data;
288
+ }
289
+ }
290
+ export default Database;
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "rengandb",
3
+ "version": "1.0.2",
4
+ "description": "RenganDB Database Module",
5
+ "author": {
6
+ "name": "RengaN"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/RengaN02/rengandb.git"
11
+ },
12
+ "type": "module",
13
+ "main": "build/main.js",
14
+ "types": "build/main.d.ts",
15
+ "files": [
16
+ "build/",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc"
22
+ },
23
+ "dependencies": {
24
+ "chokidar": "^4.0.0",
25
+ "lodash": "^4.17.21",
26
+ "proper-lockfile": "^4.1.2",
27
+ "yaml": "^2.3.4"
28
+ },
29
+ "devDependencies": {
30
+ "@types/lodash": "^4.17.24",
31
+ "@types/node": "^20.0.0",
32
+ "@types/proper-lockfile": "^4.1.4",
33
+ "typescript": "^5.0.0"
34
+ }
35
+ }