memory-adapter.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. import {Adapter} from '../adapter.js';
  2. import {cloneDeep} from '../../utils/index.js';
  3. import {capitalize} from '../../utils/index.js';
  4. import {DataType} from '../../definition/index.js';
  5. import {SliceClauseTool} from '../../filter/index.js';
  6. import {WhereClauseTool} from '../../filter/index.js';
  7. import {OrderClauseTool} from '../../filter/index.js';
  8. import {InvalidArgumentError} from '../../errors/index.js';
  9. import {ModelDefinitionUtils} from '../../definition/index.js';
  10. /**
  11. * Memory adapter.
  12. */
  13. export class MemoryAdapter extends Adapter {
  14. /**
  15. * Tables.
  16. *
  17. * @type {Map<string, Map<number, Record<string, any>>>}
  18. */
  19. _tables = new Map();
  20. /**
  21. * Last ids.
  22. *
  23. * @type {Map<string, number>}
  24. */
  25. _lastIds = new Map();
  26. /**
  27. * Get table or create.
  28. *
  29. * @param {string} modelName
  30. * @returns {Map<number, object>}
  31. */
  32. _getTableOrCreate(modelName) {
  33. const tableName =
  34. this.getService(ModelDefinitionUtils).getTableNameByModelName(modelName);
  35. let table = this._tables.get(tableName);
  36. if (table) return table;
  37. table = new Map();
  38. this._tables.set(tableName, table);
  39. return table;
  40. }
  41. /**
  42. * Gen next id value.
  43. *
  44. * @param {string} modelName
  45. * @param {string} propName
  46. * @returns {number}
  47. */
  48. _genNextIdValue(modelName, propName) {
  49. const modelUtils = this.getService(ModelDefinitionUtils);
  50. const propType = modelUtils.getDataTypeByPropertyName(modelName, propName);
  51. if (propType !== DataType.ANY && propType !== DataType.NUMBER) {
  52. throw new InvalidArgumentError(
  53. 'The memory adapter able to generate only Number identifiers, ' +
  54. 'but the primary key %v of the model %v is defined as %s. ' +
  55. 'Do provide your own value for the %v property, or change the type ' +
  56. 'in the primary key definition to a Number that will be ' +
  57. 'generated automatically.',
  58. propName,
  59. modelName,
  60. capitalize(propType),
  61. propName,
  62. );
  63. }
  64. const tableName = modelUtils.getTableNameByModelName(modelName);
  65. const table = this._getTableOrCreate(modelName);
  66. let nextId = this._lastIds.get(tableName) ?? 0;
  67. do {
  68. nextId++;
  69. } while (table.has(nextId));
  70. this._lastIds.set(tableName, nextId);
  71. return nextId;
  72. }
  73. /**
  74. * Update last id value if needed.
  75. *
  76. * Если переданное значение последнего использованного
  77. * идентификатора больше текущего, то текущее значение
  78. * перезаписывается полученным.
  79. *
  80. * @param {string} modelName
  81. * @param {number} idValue
  82. */
  83. _updateLastIdValueIfNeeded(modelName, idValue) {
  84. const tableName =
  85. this.getService(ModelDefinitionUtils).getTableNameByModelName(modelName);
  86. const currentLastId = this._lastIds.get(tableName) ?? 0;
  87. if (idValue > currentLastId) {
  88. this._lastIds.set(tableName, idValue);
  89. }
  90. }
  91. /**
  92. * Create
  93. *
  94. * @param {string} modelName
  95. * @param {object} modelData
  96. * @param {object|undefined} filter
  97. * @returns {Promise<object>}
  98. */
  99. // eslint-disable-next-line no-unused-vars
  100. async create(modelName, modelData, filter = undefined) {
  101. const pkPropName =
  102. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  103. modelName,
  104. );
  105. let idValue = modelData[pkPropName];
  106. if (idValue == null || idValue === '' || idValue === 0) {
  107. idValue = this._genNextIdValue(modelName, pkPropName);
  108. }
  109. // если идентификатор передан вручную и является числом,
  110. // то значение последнего использованного идентификатора
  111. // обновляется на полученное
  112. else if (typeof idValue === 'number') {
  113. this._updateLastIdValueIfNeeded(modelName, idValue);
  114. }
  115. const table = this._getTableOrCreate(modelName);
  116. if (table.has(idValue))
  117. throw new InvalidArgumentError(
  118. 'The value %v of the primary key %v already exists in the model %v.',
  119. idValue,
  120. pkPropName,
  121. modelName,
  122. );
  123. modelData = cloneDeep(modelData);
  124. modelData[pkPropName] = idValue;
  125. const tableData = this.getService(
  126. ModelDefinitionUtils,
  127. ).convertPropertyNamesToColumnNames(modelName, modelData);
  128. table.set(idValue, tableData);
  129. return this.getService(
  130. ModelDefinitionUtils,
  131. ).convertColumnNamesToPropertyNames(modelName, tableData);
  132. }
  133. /**
  134. * Replace by id.
  135. *
  136. * @param {string} modelName
  137. * @param {string|number} id
  138. * @param {object} modelData
  139. * @param {object|undefined} filter
  140. * @returns {Promise<object>}
  141. */
  142. // eslint-disable-next-line no-unused-vars
  143. async replaceById(modelName, id, modelData, filter = undefined) {
  144. const table = this._getTableOrCreate(modelName);
  145. const isExists = table.has(id);
  146. const pkPropName =
  147. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  148. modelName,
  149. );
  150. if (!isExists)
  151. throw new InvalidArgumentError(
  152. 'The value %v of the primary key %v does not exist in the model %v.',
  153. id,
  154. pkPropName,
  155. modelName,
  156. );
  157. modelData = cloneDeep(modelData);
  158. modelData[pkPropName] = id;
  159. const tableData = this.getService(
  160. ModelDefinitionUtils,
  161. ).convertPropertyNamesToColumnNames(modelName, modelData);
  162. table.set(id, tableData);
  163. return this.getService(
  164. ModelDefinitionUtils,
  165. ).convertColumnNamesToPropertyNames(modelName, tableData);
  166. }
  167. /**
  168. * Replace or create.
  169. *
  170. * @param {string} modelName
  171. * @param {object} modelData
  172. * @param {object|undefined} filter
  173. * @returns {Promise<object>}
  174. */
  175. // eslint-disable-next-line no-unused-vars
  176. async replaceOrCreate(modelName, modelData, filter = undefined) {
  177. const pkPropName =
  178. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  179. modelName,
  180. );
  181. let idValue = modelData[pkPropName];
  182. if (idValue == null || idValue === '' || idValue === 0) {
  183. idValue = this._genNextIdValue(modelName, pkPropName);
  184. }
  185. // если идентификатор передан вручную и является числом,
  186. // то значение последнего использованного идентификатора
  187. // обновляется на полученное
  188. else if (typeof idValue === 'number') {
  189. this._updateLastIdValueIfNeeded(modelName, idValue);
  190. }
  191. const table = this._getTableOrCreate(modelName);
  192. modelData = cloneDeep(modelData);
  193. modelData[pkPropName] = idValue;
  194. const tableData = this.getService(
  195. ModelDefinitionUtils,
  196. ).convertPropertyNamesToColumnNames(modelName, modelData);
  197. table.set(idValue, tableData);
  198. return this.getService(
  199. ModelDefinitionUtils,
  200. ).convertColumnNamesToPropertyNames(modelName, tableData);
  201. }
  202. /**
  203. * Patch.
  204. *
  205. * @param {string} modelName
  206. * @param {object} modelData
  207. * @param {object|undefined} where
  208. * @returns {Promise<number>}
  209. */
  210. async patch(modelName, modelData, where = undefined) {
  211. const table = this._getTableOrCreate(modelName);
  212. const tableItems = Array.from(table.values());
  213. if (!tableItems.length) return 0;
  214. let modelItems = tableItems.map(tableItem =>
  215. this.getService(ModelDefinitionUtils).convertColumnNamesToPropertyNames(
  216. modelName,
  217. tableItem,
  218. ),
  219. );
  220. if (where && typeof where === 'object')
  221. modelItems = this.getService(WhereClauseTool).filter(modelItems, where);
  222. const size = modelItems.length;
  223. const pkPropName =
  224. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  225. modelName,
  226. );
  227. modelData = cloneDeep(modelData);
  228. delete modelData[pkPropName];
  229. modelItems.forEach(existingModelData => {
  230. const mergedModelData = Object.assign({}, existingModelData, modelData);
  231. const mergedTableData = this.getService(
  232. ModelDefinitionUtils,
  233. ).convertPropertyNamesToColumnNames(modelName, mergedModelData);
  234. const idValue = existingModelData[pkPropName];
  235. table.set(idValue, mergedTableData);
  236. });
  237. return size;
  238. }
  239. /**
  240. * Patch by id.
  241. *
  242. * @param {string} modelName
  243. * @param {string|number} id
  244. * @param {object} modelData
  245. * @param {object|undefined} filter
  246. * @returns {Promise<object>}
  247. */
  248. // eslint-disable-next-line no-unused-vars
  249. async patchById(modelName, id, modelData, filter = undefined) {
  250. const table = this._getTableOrCreate(modelName);
  251. const existingTableData = table.get(id);
  252. const pkPropName =
  253. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  254. modelName,
  255. );
  256. if (existingTableData == null)
  257. throw new InvalidArgumentError(
  258. 'The value %v of the primary key %v does not exist in the model %v.',
  259. id,
  260. pkPropName,
  261. modelName,
  262. );
  263. modelData = cloneDeep(modelData);
  264. delete modelData[pkPropName];
  265. const existingModelData = this.getService(
  266. ModelDefinitionUtils,
  267. ).convertColumnNamesToPropertyNames(modelName, existingTableData);
  268. const mergedModelData = Object.assign({}, existingModelData, modelData);
  269. const mergedTableData = this.getService(
  270. ModelDefinitionUtils,
  271. ).convertPropertyNamesToColumnNames(modelName, mergedModelData);
  272. table.set(id, mergedTableData);
  273. return this.getService(
  274. ModelDefinitionUtils,
  275. ).convertColumnNamesToPropertyNames(modelName, mergedTableData);
  276. }
  277. /**
  278. * Find.
  279. *
  280. * @param {string} modelName
  281. * @param {object|undefined} filter
  282. * @returns {Promise<object[]>}
  283. */
  284. async find(modelName, filter = undefined) {
  285. const table = this._getTableOrCreate(modelName);
  286. const tableItems = Array.from(table.values());
  287. let modelItems = tableItems.map(tableItem =>
  288. this.getService(ModelDefinitionUtils).convertColumnNamesToPropertyNames(
  289. modelName,
  290. tableItem,
  291. ),
  292. );
  293. if (filter && typeof filter === 'object') {
  294. if (filter.where)
  295. modelItems = this.getService(WhereClauseTool).filter(
  296. modelItems,
  297. filter.where,
  298. );
  299. if (filter.skip || filter.limit)
  300. modelItems = this.getService(SliceClauseTool).slice(
  301. modelItems,
  302. filter.skip,
  303. filter.limit,
  304. );
  305. if (filter.order)
  306. this.getService(OrderClauseTool).sort(modelItems, filter.order);
  307. }
  308. return modelItems;
  309. }
  310. /**
  311. * Find by id.
  312. *
  313. * @param {string} modelName
  314. * @param {string|number} id
  315. * @param {object|undefined} filter
  316. * @returns {Promise<object>}
  317. */
  318. // eslint-disable-next-line no-unused-vars
  319. async findById(modelName, id, filter = undefined) {
  320. const table = this._getTableOrCreate(modelName);
  321. const tableData = table.get(id);
  322. const pkPropName =
  323. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  324. modelName,
  325. );
  326. if (!tableData)
  327. throw new InvalidArgumentError(
  328. 'The value %v of the primary key %v does not exist in the model %v.',
  329. id,
  330. pkPropName,
  331. modelName,
  332. );
  333. return this.getService(
  334. ModelDefinitionUtils,
  335. ).convertColumnNamesToPropertyNames(modelName, tableData);
  336. }
  337. /**
  338. * Delete.
  339. *
  340. * @param {string} modelName
  341. * @param {object|undefined} where
  342. * @returns {Promise<number>}
  343. */
  344. async delete(modelName, where = undefined) {
  345. const table = this._getTableOrCreate(modelName);
  346. const tableItems = Array.from(table.values());
  347. if (!tableItems.length) return 0;
  348. let modelItems = tableItems.map(tableItem =>
  349. this.getService(ModelDefinitionUtils).convertColumnNamesToPropertyNames(
  350. modelName,
  351. tableItem,
  352. ),
  353. );
  354. if (where && typeof where === 'object')
  355. modelItems = this.getService(WhereClauseTool).filter(modelItems, where);
  356. const size = modelItems.length;
  357. const idPropName =
  358. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  359. modelName,
  360. );
  361. modelItems.forEach(modelData => {
  362. const idValue = modelData[idPropName];
  363. table.delete(idValue);
  364. });
  365. return size;
  366. }
  367. /**
  368. * Delete by id.
  369. *
  370. * @param {string} modelName
  371. * @param {string|number} id
  372. * @returns {Promise<boolean>}
  373. */
  374. async deleteById(modelName, id) {
  375. const table = this._getTableOrCreate(modelName);
  376. const isExists = table.has(id);
  377. table.delete(id);
  378. return isExists;
  379. }
  380. /**
  381. * Exists.
  382. *
  383. * @param {string} modelName
  384. * @param {string|number} id
  385. * @returns {Promise<boolean>}
  386. */
  387. async exists(modelName, id) {
  388. const table = this._getTableOrCreate(modelName);
  389. return table.has(id);
  390. }
  391. /**
  392. * Count.
  393. *
  394. * @param {string} modelName
  395. * @param {object|undefined} where
  396. * @returns {Promise<number>}
  397. */
  398. async count(modelName, where = undefined) {
  399. const table = this._getTableOrCreate(modelName);
  400. const tableItems = Array.from(table.values());
  401. let modelItems = tableItems.map(tableItem =>
  402. this.getService(ModelDefinitionUtils).convertColumnNamesToPropertyNames(
  403. modelName,
  404. tableItem,
  405. ),
  406. );
  407. if (where && typeof where === 'object')
  408. modelItems = this.getService(WhereClauseTool).filter(modelItems, where);
  409. return modelItems.length;
  410. }
  411. }