UNPKG

11.8 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.JsonDB = void 0;
4const Utils_1 = require("./lib/Utils");
5const FS = require("fs");
6const path = require("path");
7const mkdirp = require("mkdirp");
8const Errors_1 = require("./lib/Errors");
9const DBParentData_1 = require("./lib/DBParentData");
10const ArrayInfo_1 = require("./lib/ArrayInfo");
11const JsonDBConfig_1 = require("./lib/JsonDBConfig");
12class JsonDB {
13 /**
14 * JSONDB Constructor
15 * @param filename where to save the "DB". Can also be used to give the whole configuration
16 * @param saveOnPush save the database at each push command into the json file
17 * @param humanReadable the JSON file will be readable easily by a human
18 * @param separator what to use as separator
19 */
20 constructor(filename, saveOnPush = true, humanReadable = false, separator = '/') {
21 this.loaded = false;
22 this.data = {};
23 if (filename instanceof JsonDBConfig_1.Config) {
24 this.config = filename;
25 }
26 else {
27 this.config = new JsonDBConfig_1.Config(filename, saveOnPush, humanReadable, separator);
28 }
29 if (!FS.existsSync(this.config.filename)) {
30 const dirname = path.dirname(this.config.filename);
31 mkdirp.sync(dirname);
32 this.save(true);
33 this.loaded = true;
34 }
35 }
36 /**
37 * Process datapath into different parts
38 * @param dataPath
39 */
40 processDataPath(dataPath) {
41 if (dataPath === undefined || !dataPath.trim()) {
42 throw new Errors_1.DataError("The Data Path can't be empty", 6);
43 }
44 if (dataPath == this.config.separator) {
45 return [];
46 }
47 dataPath = Utils_1.removeTrailingChar(dataPath, this.config.separator);
48 const path = dataPath.split(this.config.separator);
49 path.shift();
50 return path;
51 }
52 retrieveData(dataPath, create = false) {
53 this.load();
54 const thisDb = this;
55 const recursiveProcessDataPath = (data, index) => {
56 let property = dataPath[index];
57 /**
58 * Find the wanted Data or create it.
59 */
60 function findData(isArray = false) {
61 if (data.hasOwnProperty(property)) {
62 data = data[property];
63 }
64 else if (create) {
65 if (isArray) {
66 data[property] = [];
67 }
68 else {
69 data[property] = {};
70 }
71 data = data[property];
72 }
73 else {
74 throw new Errors_1.DataError(`Can't find dataPath: ${thisDb.config.separator}${dataPath.join(thisDb.config.separator)}. Stopped at ${property}`, 5);
75 }
76 }
77 const arrayInfo = ArrayInfo_1.ArrayInfo.processArray(property);
78 if (arrayInfo) {
79 property = arrayInfo.property;
80 findData(true);
81 if (!Array.isArray(data)) {
82 throw new Errors_1.DataError(`DataPath: ${thisDb.config.separator}${dataPath.join(thisDb.config.separator)}. ${property} is not an array.`, 11);
83 }
84 const arrayIndex = arrayInfo.getIndex(data, true);
85 if (!arrayInfo.append && data.hasOwnProperty(arrayIndex)) {
86 data = data[arrayIndex];
87 }
88 else if (create) {
89 if (arrayInfo.append) {
90 data.push({});
91 data = data[data.length - 1];
92 }
93 else {
94 data[arrayIndex] = {};
95 data = data[arrayIndex];
96 }
97 }
98 else {
99 throw new Errors_1.DataError(`DataPath: ${thisDb.config.separator}${dataPath.join(thisDb.config.separator)}. . Can't find index ${arrayInfo.index} in array ${property}`, 10);
100 }
101 }
102 else {
103 findData();
104 }
105 if (dataPath.length == ++index) {
106 return data;
107 }
108 return recursiveProcessDataPath(data, index);
109 };
110 if (dataPath.length === 0) {
111 return this.data;
112 }
113 return recursiveProcessDataPath(this.data, 0);
114 }
115 getParentData(dataPath, create) {
116 const path = this.processDataPath(dataPath);
117 const last = path.pop();
118 return new DBParentData_1.DBParentData(this.retrieveData(path, create), this, dataPath, last);
119 }
120 /**
121 * Get the wanted data
122 * @param dataPath path of the data to retrieve
123 */
124 getData(dataPath) {
125 const path = this.processDataPath(dataPath);
126 return this.retrieveData(path, false);
127 }
128 /**
129 * Same as getData only here it's directly typed to your object
130 * @param dataPath path of the data to retrieve
131 */
132 getObject(dataPath) {
133 return this.getData(dataPath);
134 }
135 /**
136 * Check for existing datapath
137 * @param dataPath
138 */
139 exists(dataPath) {
140 try {
141 this.getData(dataPath);
142 return true;
143 }
144 catch (e) {
145 if (e instanceof Errors_1.DataError) {
146 return false;
147 }
148 throw e;
149 }
150 }
151 /**
152 * Returns the number of element which constitutes the array
153 * @param dataPath
154 */
155 count(dataPath) {
156 const result = this.getData(dataPath);
157 if (!Array.isArray(result)) {
158 throw new Errors_1.DataError(`DataPath: ${dataPath} is not an array.`, 11);
159 }
160 const path = this.processDataPath(dataPath);
161 const data = this.retrieveData(path, false);
162 return data.length;
163 }
164 /**
165 * Returns the index of the object that meets the criteria submitted.
166 * @param dataPath base dataPath from where to start searching
167 * @param searchValue value to look for in the dataPath
168 * @param propertyName name of the property to look for searchValue
169 */
170 getIndex(dataPath, searchValue, propertyName = 'id') {
171 const result = this.getData(dataPath);
172 if (!Array.isArray(result)) {
173 throw new Errors_1.DataError(`DataPath: ${dataPath} is not an array.`, 11);
174 }
175 const path = this.processDataPath(dataPath);
176 const data = this.retrieveData(path, false);
177 return data.map(function (element) { return element[propertyName]; }).indexOf(searchValue);
178 }
179 /**
180 * Find all specific entry in an array/object
181 * @param rootPath base dataPath from where to start searching
182 * @param callback method to filter the result and find the wanted entry. Receive the entry and it's index.
183 */
184 filter(rootPath, callback) {
185 const result = this.getData(rootPath);
186 if (Array.isArray(result)) {
187 return result.filter(callback);
188 }
189 if (result instanceof Object) {
190 const entries = Object.entries(result);
191 const found = entries.filter((entry) => {
192 return callback(entry[1], entry[0]);
193 });
194 if (!found || found.length < 1) {
195 return undefined;
196 }
197 return found.map((entry) => {
198 return entry[1];
199 });
200 }
201 throw new Errors_1.DataError("The entry at the path (" + rootPath + ") needs to be either an Object or an Array", 12);
202 }
203 /**
204 * Find a specific entry in an array/object
205 * @param rootPath base dataPath from where to start searching
206 * @param callback method to filter the result and find the wanted entry. Receive the entry and it's index.
207 */
208 find(rootPath, callback) {
209 const result = this.getData(rootPath);
210 if (Array.isArray(result)) {
211 return result.find(callback);
212 }
213 if (result instanceof Object) {
214 const entries = Object.entries(result);
215 const found = entries.find((entry) => {
216 return callback(entry[1], entry[0]);
217 });
218 if (!found || found.length < 2) {
219 return undefined;
220 }
221 return found[1];
222 }
223 throw new Errors_1.DataError("The entry at the path (" + rootPath + ") needs to be either an Object or an Array", 12);
224 }
225 /**
226 * Pushing data into the database
227 * @param dataPath path leading to the data
228 * @param data data to push
229 * @param override overriding or not the data, if not, it will merge them
230 */
231 push(dataPath, data, override = true) {
232 const dbData = this.getParentData(dataPath, true);
233 if (!dbData) {
234 throw new Error("Data not found");
235 }
236 let toSet = data;
237 if (!override) {
238 if (Array.isArray(data)) {
239 let storedData = dbData.getData();
240 if (storedData === undefined) {
241 storedData = [];
242 }
243 else if (!Array.isArray(storedData)) {
244 throw new Errors_1.DataError("Can't merge another type of data with an Array", 3);
245 }
246 toSet = storedData.concat(data);
247 }
248 else if (data === Object(data)) {
249 if (Array.isArray(dbData.getData())) {
250 throw new Errors_1.DataError("Can't merge an Array with an Object", 4);
251 }
252 toSet = Utils_1.merge(dbData.getData(), data);
253 }
254 }
255 dbData.setData(toSet);
256 if (this.config.saveOnPush) {
257 this.save();
258 }
259 }
260 /**
261 * Delete the data
262 * @param dataPath path leading to the data
263 */
264 delete(dataPath) {
265 const dbData = this.getParentData(dataPath, true);
266 if (!dbData) {
267 return;
268 }
269 dbData.delete();
270 if (this.config.saveOnPush) {
271 this.save();
272 }
273 }
274 /**
275 * Only use this if you know what you're doing.
276 * It reset the full data of the database.
277 * @param data
278 */
279 resetData(data) {
280 this.data = data;
281 }
282 /**
283 * Reload the database from the file
284 */
285 reload() {
286 this.loaded = false;
287 this.load();
288 }
289 ;
290 /**
291 * Manually load the database
292 * It is automatically called when the first getData is done
293 */
294 load() {
295 if (this.loaded) {
296 return;
297 }
298 try {
299 const data = FS.readFileSync(this.config.filename, 'utf8');
300 this.data = JSON.parse(data);
301 this.loaded = true;
302 }
303 catch (err) {
304 const error = new Errors_1.DatabaseError("Can't Load Database", 1, err);
305 throw error;
306 }
307 }
308 /**
309 * Manually save the database
310 * By default you can't save the database if it's not loaded
311 * @param force force the save of the database
312 */
313 save(force) {
314 force = force || false;
315 if (!force && !this.loaded) {
316 throw new Errors_1.DatabaseError("DataBase not loaded. Can't write", 7);
317 }
318 let data = "";
319 try {
320 if (this.config.humanReadable) {
321 data = JSON.stringify(this.data, null, 4);
322 }
323 else {
324 data = JSON.stringify(this.data);
325 }
326 FS.writeFileSync(this.config.filename, data, 'utf8');
327 }
328 catch (err) {
329 const error = new Errors_1.DatabaseError("Can't save the database", 2, err);
330 throw error;
331 }
332 }
333}
334exports.JsonDB = JsonDB;
335//# sourceMappingURL=JsonDB.js.map
\No newline at end of file