UNPKG

gypsum

Version:

Simple and easy lightweight typescript server side framework on Node.js.

370 lines 17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const Validall = require("validall"); const object_1 = require("tools-box/object"); const string_1 = require("tools-box/string"); const state_1 = require("../../state"); const logger_1 = require("../../misc/logger"); const types_1 = require("../../types"); class FileCollection { constructor(name, schema, options) { this.findOptions = { skip: -1, limit: -1 }; this.updateOptions = { returnDoc: false, multi: true }; this.deleteOptions = { returnDoc: false, limit: -1 }; let dir = state_1.State.config.files_data_dir; if (!fs.existsSync(dir)) fs.mkdirSync(dir); this.filename = name; this.filePath = `${dir}/${name}`; this.schema = schema || null; this.logger = new logger_1.Logger(name); if (options) { Object.assign(this.findOptions, options.findOptions || {}); Object.assign(this.updateOptions, options.updateOptions || {}); Object.assign(this.deleteOptions, options.deleteOptions || {}); } try { fs.openSync(this.filePath, 'r'); } catch (openError) { try { fs.writeFileSync(this.filePath, '{"indexes":[],"documents":[]}'); } catch (writeError) { this.logger.error('error creating file' + this.filePath, writeError); } } } read() { return new Promise((resolve, reject) => { fs.readFile(this.filePath, 'utf-8', (err, data) => { if (err) { this.logger.error(`Error reading file: '${this.filePath}':`, err); return reject({ message: `Error reading file: '${this.filePath}'`, original: err, code: types_1.RESPONSE_CODES.UNKNOWN_ERROR }); } if (data) { try { let parsedData = JSON.parse(data); resolve(parsedData); } catch (parseError) { this.logger.error(`Error parsing file: '${this.filePath}':`, parseError); reject({ message: `Error parsing file: '${this.filePath}'`, original: parseError, code: types_1.RESPONSE_CODES.UNKNOWN_ERROR }); } } else { resolve({ indexes: [], documents: [] }); } }); }); } write(data) { return new Promise((resolve, reject) => { let dataString = ''; try { dataString = JSON.stringify(data); } catch (stringifyError) { this.logger.error(`Error stringifying file: '${this.filePath}':`, stringifyError); reject({ message: `Error stringifying file: '${this.filePath}'`, original: stringifyError, code: types_1.RESPONSE_CODES.UNKNOWN_ERROR }); } fs.writeFile(this.filePath, dataString, err => { if (err) { this.logger.error(`Error saving file: '${this.filePath}':`, err); reject({ message: `Error saving file: '${this.filePath}'`, original: err, code: types_1.RESPONSE_CODES.UNKNOWN_ERROR }); } else { resolve(true); } }); }); } find(query, projection, options) { options = Object.assign({}, this.findOptions, options || {}); return new Promise((resolve, reject) => { this.read() .then((data) => { let documents = data.documents; if (!documents || !documents.length) return resolve([]); let result = []; let method = 'pick'; let projectionList = []; if (projection) { projectionList = projection.split(','); if (projectionList[0] === '-') { method = 'omit'; projectionList = projectionList.slice(1); } } for (let i = 0; i < documents.length; i++) { let state = Validall(documents[i], query); if (state) { if (options.skip-- > 0) continue; if (projectionList.length) result.push(method === "omit" ? object_1.omit(documents[i], projectionList) : object_1.pick(documents[i], projectionList)); else result.push(documents[i]); if (--options.limit) break; } } if (!this.schema) return resolve(result); let selects = this.schema.getPropsByName('select'); selects = selects.filter((item) => item.value === false).map((item) => item.path); if (selects && selects.length) for (let i = 0; i < result.length; i++) object_1.omit(result[i], selects); resolve(result); }) .catch(error => reject(error)); }); } findById(id, projection) { return this.find({ _id: id }, projection, { limit: 1 }) .then(docs => { return docs[0]; }); } findOne(query, projection) { return this.find(query, projection, { limit: 1 }) .then(docs => { return docs[0]; }); } count(query) { return new Promise((resolve, reject) => { this.read() .then((data) => { let documents = data.documents; if (!documents || documents.length) return resolve(0); if (Object.keys(query).length === 0) return resolve(documents.length); let count = 0; for (let i = 0; i < documents.length; i++) if (Validall(documents[0], query)) count++; resolve(count); }) .catch(error => reject(error)); }); } insert(documents) { return new Promise((resolve, reject) => { this.read() .then((data) => { if (documents && !Array.isArray(documents)) documents = [documents]; if (!documents || !documents.length) return resolve([]); for (let i = 0; i < documents.length; i++) { let document = documents[i]; delete document._id; if (this.schema) { if (!this.schema.test(document)) return reject({ message: this.schema.error.message, original: this.schema.error, code: types_1.RESPONSE_CODES.BAD_REQUEST }); let props = this.schema.getProps(); let internals = []; for (let prop in props) if (props[prop].internal) internals.push(prop); if (internals.length) { for (let i = 0; i < internals.length; i++) if (this.schema.defaults[internals[i]] !== undefined) if (object_1.getValue(document, internals[i]) !== this.schema.defaults[internals[i]]) return reject({ message: `[${this.filename}]: '${internals[i]}' cannot be set externaly!`, code: types_1.RESPONSE_CODES.UNAUTHORIZED }); else if (object_1.getValue(document, internals[i]) !== undefined) return reject({ message: `[${this.filename}]: '${internals[i]}' cannot be set externaly!`, code: types_1.RESPONSE_CODES.UNAUTHORIZED }); } while (true) { let id = string_1.random(); if (data.indexes.indexOf(id) === -1) { document._id = id; break; } } data.indexes.push(document._id); data.documents.push(document); } } this.write(data) .then(() => { resolve(documents); if (!this.schema) return resolve(documents); let selects = this.schema.getPropsByName('select'); selects = selects.filter((item) => item.value === false).map((item) => item.path); if (selects && selects.length) for (let i = 0; i < documents.length; i++) object_1.omit(documents[i], selects); resolve(documents); }) .catch(error => reject(error)); }) .catch(error => reject(error)); }); } update(filter, update, options, single = false) { if (options) options = Object.assign({}, this.updateOptions, options); return new Promise((resolve, reject) => { if (!Validall.Types.object(update) || !Object.keys(update).length) return reject({ message: `invalid update data: ${update}`, code: types_1.RESPONSE_CODES.BAD_REQUEST }); delete update._id; this.read() .then((data) => { let result = 0; let docs = []; for (let i = 0; i < data.documents.length; i++) { if (Validall(data.documents[i], filter)) { result++; let updatedDoc = {}; object_1.extend(updatedDoc, data.documents[i]); if (Validall.Types.primitive(data.documents[i])) updatedDoc = update; else object_1.extend(updatedDoc, update); if (this.schema) { if (!this.schema.test(updatedDoc)) return reject({ message: this.schema.error.message, original: this.schema.error, code: types_1.RESPONSE_CODES.BAD_REQUEST }); let props = this.schema.getProps(); if (Object.keys(props).length) { let constants = [], internals = []; for (let prop in props) { if (props[prop].constant) constants.push(prop); else if (props[prop].internal) { internals.push(prop); } } if (constants.length) { let changedField = object_1.compareValues(constants, data.documents[i], updatedDoc); if (changedField) return reject({ message: `[${this.filename}]: '${changedField}' is a constant field that cannot be changed!`, code: types_1.RESPONSE_CODES.UNAUTHORIZED }); } if (internals.length) { let changedField = object_1.compareValues(internals, data.documents[i], updatedDoc); if (changedField) return reject({ message: `[${this.filename}]: '${changedField}' cannot be modified externaly!`, code: types_1.RESPONSE_CODES.UNAUTHORIZED }); } } } data.documents[i] = updatedDoc; docs.push(updatedDoc); if (!options.multi) break; } } if (result) this.write(data) .then(() => { if (options && options.returnDoc) { if (!this.schema) return resolve(single ? docs[0] : docs); let selects = this.schema.getPropsByName('select'); selects = selects.filter((item) => item.value === false).map((item) => item.path); if (selects && selects.length) for (let i = 0; i < docs.length; i++) object_1.omit(docs[i], selects); resolve(single ? docs[0] : docs); } else { resolve(result); } }) .catch(error => reject(error)); }) .catch(error => reject(error)); }); } updateById(id, update) { return this.update({ _id: id }, update, { multi: false, returnDoc: true }, true) .then((docs) => { if (docs && docs.length) return docs[0]; else return null; }); } updateOne(filter, update) { return this.update(filter, update, { multi: false, returnDoc: true }, true) .then((docs) => { if (docs && docs.length) return docs[0]; else return null; }); } delete(filter, options) { if (options) options = Object.assign({}, this.deleteOptions, options); return new Promise((resolve, reject) => { let limit = options ? options.limit || 1 : 1; this.read() .then((data) => { let result = 0; let docs = []; for (let i = 0; i < data.documents.length; i++) { if (Validall(data.documents[i], filter)) { result++; let idIndex = data.indexes.findIndex(index => index === data.documents[i]._id); if (idIndex > -1) data.indexes.splice(idIndex, 1); docs.push(data.documents.splice(i--, 1)[0]); if (--limit === 0) break; } } if (result) this.write(data) .then(() => resolve((options && options.returnDoc) ? docs : result)) .catch(error => reject(error)); }) .catch(error => reject(error)); }); } deleteById(id) { return this.delete({ _id: id }, { limit: 1, returnDoc: true }) .then((docs) => { if (docs && docs.length) return docs[0]; else return {}; }); } deleteOne(filter) { return this.delete(filter, { limit: 1, returnDoc: true }) .then((docs) => { if (docs && docs.length) return docs[0]; else return {}; }); } } exports.FileCollection = FileCollection; //# sourceMappingURL=collection.js.map