nedb.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import { parse as parserExpression } from 'pivotql-parser-expression';
  2. import { compile as compilerMongodb } from 'pivotql-compiler-mongodb';
  3. import { throwError } from '../../error.js';
  4. import { uid } from '../../uid.js';
  5. import { DEFAULT_BOX_OPTIONS } from './utils.js';
  6. // Nedb backend for proof of concept
  7. export const NeDBBackend = (options) => {
  8. const db = {};
  9. let _Datastore;
  10. const getBoxDB = async (boxId) => {
  11. if (!db[boxId]) {
  12. if (!_Datastore) {
  13. try {
  14. _Datastore = (await import('@seald-io/nedb')).default;
  15. } catch (e) {
  16. throw new Error(
  17. 'You must install "nedb" package in order to be able to use the NeDBStoreBackend!'
  18. );
  19. }
  20. }
  21. db[boxId] = new _Datastore({
  22. filename: `${options.dirname}/${boxId}.json`,
  23. ...options,
  24. autoload: true,
  25. });
  26. }
  27. return db[boxId];
  28. };
  29. const getBoxOption = async (boxId) => {
  30. const boxes = await getBoxDB('boxes');
  31. return new Promise((resolve, reject) => {
  32. boxes.findOne({ box: boxId }, (err, doc) => {
  33. if (err) {
  34. /* istanbul ignore next */
  35. reject(err);
  36. }
  37. resolve(doc || undefined);
  38. });
  39. });
  40. };
  41. return {
  42. getBoxOption,
  43. async createOrUpdateBox(boxId, options = { ...DEFAULT_BOX_OPTIONS }) {
  44. const prevOptions = (await getBoxOption(boxId)) || {};
  45. const boxes = await getBoxDB('boxes');
  46. return new Promise((resolve, reject) => {
  47. boxes.update(
  48. { box: boxId },
  49. { ...prevOptions, ...options, box: boxId },
  50. { upsert: true },
  51. (err, doc) => {
  52. if (err) {
  53. /* istanbul ignore next */
  54. reject(err);
  55. }
  56. resolve(doc);
  57. }
  58. );
  59. });
  60. },
  61. async list(
  62. boxId,
  63. {
  64. limit = 50,
  65. sort = '_id',
  66. asc = true,
  67. skip = 0,
  68. onlyFields = [],
  69. q,
  70. } = {}
  71. ) {
  72. const boxRecord = await getBoxOption(boxId);
  73. if (!boxRecord) {
  74. throwError('Box not found', 404);
  75. }
  76. const boxDB = await getBoxDB(boxId);
  77. let filter = {};
  78. if (q) {
  79. try {
  80. filter = compilerMongodb(parserExpression(q));
  81. } catch (e) {
  82. throwError('Invalid query expression.', 400);
  83. }
  84. }
  85. return new Promise((resolve, reject) => {
  86. boxDB
  87. .find(
  88. filter,
  89. onlyFields.length
  90. ? onlyFields.reduce((acc, field) => {
  91. acc[field] = 1;
  92. return acc;
  93. }, {})
  94. : {}
  95. )
  96. .limit(limit)
  97. .skip(skip)
  98. .sort({ [sort]: asc ? 1 : -1 })
  99. .exec((err, docs) => {
  100. if (err) {
  101. /* istanbul ignore next */
  102. reject(err);
  103. }
  104. resolve(docs);
  105. });
  106. });
  107. },
  108. async get(boxId, id) {
  109. const boxRecord = await getBoxOption(boxId);
  110. if (!boxRecord) {
  111. throwError('Box not found', 404);
  112. }
  113. const boxDB = await getBoxDB(boxId);
  114. return new Promise((resolve, reject) => {
  115. boxDB.findOne({ _id: id }, (err, doc) => {
  116. if (err) {
  117. /* istanbul ignore next */
  118. reject(err);
  119. }
  120. if (!doc) {
  121. const newError = new Error('Resource not found');
  122. newError.statusCode = 404;
  123. reject(newError);
  124. }
  125. resolve(doc);
  126. });
  127. });
  128. },
  129. async save(boxId, id, data) {
  130. const boxRecord = await getBoxOption(boxId);
  131. if (!boxRecord) {
  132. throwError('Box not found', 404);
  133. }
  134. const boxDB = await getBoxDB(boxId);
  135. const actualId = id || uid();
  136. const cleanedData = data;
  137. delete cleanedData._createdOn;
  138. delete cleanedData._modifiedOn;
  139. return new Promise((resolve, reject) => {
  140. // Creation with id or update with id
  141. boxDB.findOne({ _id: actualId }, (err, doc) => {
  142. if (err) {
  143. /* istanbul ignore next */
  144. reject(err);
  145. }
  146. if (!doc) {
  147. // Creation
  148. boxDB.insert(
  149. { ...cleanedData, _createdOn: Date.now(), _id: actualId },
  150. (err, doc) => {
  151. if (err) {
  152. /* istanbul ignore next */
  153. reject(err);
  154. }
  155. resolve(doc);
  156. }
  157. );
  158. } else {
  159. // Update
  160. boxDB.update(
  161. { _id: actualId },
  162. {
  163. ...cleanedData,
  164. _updatedOn: Date.now(),
  165. _createdOn: doc._createdOn,
  166. _id: actualId,
  167. },
  168. { returnUpdatedDocs: true },
  169. (err, numAffected, affectedDoc) => {
  170. if (!numAffected) {
  171. const newError = new Error('Resource not found');
  172. newError.statusCode = 404;
  173. reject(newError);
  174. }
  175. if (err) {
  176. /* istanbul ignore next */
  177. reject(err);
  178. }
  179. resolve(affectedDoc);
  180. }
  181. );
  182. }
  183. });
  184. });
  185. },
  186. async update(boxId, id, data) {
  187. const boxRecord = await getBoxOption(boxId);
  188. if (!boxRecord) {
  189. throwError('Box not found', 404);
  190. }
  191. const boxDB = await getBoxDB(boxId);
  192. const cleanedData = data;
  193. delete cleanedData._createdOn;
  194. delete cleanedData._modifiedOn;
  195. return new Promise((resolve, reject) => {
  196. boxDB.update(
  197. { _id: id },
  198. {
  199. $set: {
  200. ...cleanedData,
  201. _updatedOn: Date.now(),
  202. _id: id,
  203. },
  204. },
  205. { returnUpdatedDocs: true },
  206. (err, numAffected, affectedDoc) => {
  207. if (!numAffected) {
  208. const newError = new Error('Resource not found');
  209. newError.statusCode = 404;
  210. reject(newError);
  211. }
  212. if (err) {
  213. /* istanbul ignore next */
  214. reject(err);
  215. }
  216. resolve(affectedDoc);
  217. }
  218. );
  219. });
  220. },
  221. async delete(boxId, id) {
  222. const boxRecord = await getBoxOption(boxId);
  223. if (!boxRecord) {
  224. return 0;
  225. }
  226. const boxDB = await getBoxDB(boxId);
  227. return new Promise((resolve, reject) => {
  228. boxDB.remove({ _id: id }, {}, (err, numRemoved) => {
  229. if (err) {
  230. /* istanbul ignore next */
  231. reject(err);
  232. }
  233. resolve(numRemoved);
  234. });
  235. });
  236. },
  237. };
  238. };
  239. export default NeDBBackend;