memory-adapter.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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 propType = this.getService(
  50. ModelDefinitionUtils,
  51. ).getDataTypeByPropertyName(modelName, propName);
  52. if (propType !== DataType.ANY && propType !== DataType.NUMBER)
  53. throw new InvalidArgumentError(
  54. 'The memory adapter able to generate only Number identifiers, ' +
  55. 'but the primary key %v of the model %v is defined as %s. ' +
  56. 'Do provide your own value for the %v property, or change the type ' +
  57. 'in the primary key definition to a Number that will be ' +
  58. 'generated automatically.',
  59. propName,
  60. modelName,
  61. capitalize(propType),
  62. propName,
  63. );
  64. const tableName =
  65. this.getService(ModelDefinitionUtils).getTableNameByModelName(modelName);
  66. const lastId = this._lastIds.get(tableName) ?? 0;
  67. const nextId = lastId + 1;
  68. this._lastIds.set(tableName, nextId);
  69. const table = this._getTableOrCreate(modelName);
  70. const existedIds = Array.from(table.keys());
  71. if (existedIds.includes(nextId))
  72. return this._genNextIdValue(modelName, propName);
  73. return nextId;
  74. }
  75. /**
  76. * Create
  77. *
  78. * @param {string} modelName
  79. * @param {object} modelData
  80. * @param {object|undefined} filter
  81. * @returns {Promise<object>}
  82. */
  83. // eslint-disable-next-line no-unused-vars
  84. async create(modelName, modelData, filter = undefined) {
  85. const pkPropName =
  86. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  87. modelName,
  88. );
  89. let idValue = modelData[pkPropName];
  90. if (idValue == null) idValue = this._genNextIdValue(modelName, pkPropName);
  91. const table = this._getTableOrCreate(modelName);
  92. if (table.has(idValue))
  93. throw new InvalidArgumentError(
  94. 'The value %v of the primary key %v already exists in the model %v.',
  95. idValue,
  96. pkPropName,
  97. modelName,
  98. );
  99. modelData = cloneDeep(modelData);
  100. modelData[pkPropName] = idValue;
  101. const tableData = this.getService(
  102. ModelDefinitionUtils,
  103. ).convertPropertyNamesToColumnNames(modelName, modelData);
  104. table.set(idValue, tableData);
  105. return this.getService(
  106. ModelDefinitionUtils,
  107. ).convertColumnNamesToPropertyNames(modelName, tableData);
  108. }
  109. /**
  110. * Replace by id.
  111. *
  112. * @param {string} modelName
  113. * @param {string|number} id
  114. * @param {object} modelData
  115. * @param {object|undefined} filter
  116. * @returns {Promise<object>}
  117. */
  118. // eslint-disable-next-line no-unused-vars
  119. async replaceById(modelName, id, modelData, filter = undefined) {
  120. const table = this._getTableOrCreate(modelName);
  121. const isExists = table.has(id);
  122. const pkPropName =
  123. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  124. modelName,
  125. );
  126. if (!isExists)
  127. throw new InvalidArgumentError(
  128. 'The value %v of the primary key %v does not exist in the model %v.',
  129. id,
  130. pkPropName,
  131. modelName,
  132. );
  133. modelData = cloneDeep(modelData);
  134. modelData[pkPropName] = id;
  135. const tableData = this.getService(
  136. ModelDefinitionUtils,
  137. ).convertPropertyNamesToColumnNames(modelName, modelData);
  138. table.set(id, tableData);
  139. return this.getService(
  140. ModelDefinitionUtils,
  141. ).convertColumnNamesToPropertyNames(modelName, tableData);
  142. }
  143. /**
  144. * Patch.
  145. *
  146. * @param {string} modelName
  147. * @param {object} modelData
  148. * @param {object|undefined} where
  149. * @returns {Promise<number>}
  150. */
  151. async patch(modelName, modelData, where = undefined) {
  152. const table = this._getTableOrCreate(modelName);
  153. const tableItems = Array.from(table.values());
  154. if (!tableItems.length) return 0;
  155. let modelItems = tableItems.map(tableItem =>
  156. this.getService(ModelDefinitionUtils).convertColumnNamesToPropertyNames(
  157. modelName,
  158. tableItem,
  159. ),
  160. );
  161. if (where && typeof where === 'object')
  162. modelItems = this.getService(WhereClauseTool).filter(modelItems, where);
  163. const size = modelItems.length;
  164. const pkPropName =
  165. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  166. modelName,
  167. );
  168. modelData = cloneDeep(modelData);
  169. delete modelData[pkPropName];
  170. modelItems.forEach(existingModelData => {
  171. const mergedModelData = Object.assign({}, existingModelData, modelData);
  172. const mergedTableData = this.getService(
  173. ModelDefinitionUtils,
  174. ).convertPropertyNamesToColumnNames(modelName, mergedModelData);
  175. const idValue = existingModelData[pkPropName];
  176. table.set(idValue, mergedTableData);
  177. });
  178. return size;
  179. }
  180. /**
  181. * Patch by id.
  182. *
  183. * @param {string} modelName
  184. * @param {string|number} id
  185. * @param {object} modelData
  186. * @param {object|undefined} filter
  187. * @returns {Promise<object>}
  188. */
  189. // eslint-disable-next-line no-unused-vars
  190. async patchById(modelName, id, modelData, filter = undefined) {
  191. const table = this._getTableOrCreate(modelName);
  192. const existingTableData = table.get(id);
  193. const pkPropName =
  194. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  195. modelName,
  196. );
  197. if (existingTableData == null)
  198. throw new InvalidArgumentError(
  199. 'The value %v of the primary key %v does not exist in the model %v.',
  200. id,
  201. pkPropName,
  202. modelName,
  203. );
  204. modelData = cloneDeep(modelData);
  205. delete modelData[pkPropName];
  206. const existingModelData = this.getService(
  207. ModelDefinitionUtils,
  208. ).convertColumnNamesToPropertyNames(modelName, existingTableData);
  209. const mergedModelData = Object.assign({}, existingModelData, modelData);
  210. const mergedTableData = this.getService(
  211. ModelDefinitionUtils,
  212. ).convertPropertyNamesToColumnNames(modelName, mergedModelData);
  213. table.set(id, mergedTableData);
  214. return this.getService(
  215. ModelDefinitionUtils,
  216. ).convertColumnNamesToPropertyNames(modelName, mergedTableData);
  217. }
  218. /**
  219. * Find.
  220. *
  221. * @param {string} modelName
  222. * @param {object|undefined} filter
  223. * @returns {Promise<object[]>}
  224. */
  225. async find(modelName, filter = undefined) {
  226. const table = this._getTableOrCreate(modelName);
  227. const tableItems = Array.from(table.values());
  228. let modelItems = tableItems.map(tableItem =>
  229. this.getService(ModelDefinitionUtils).convertColumnNamesToPropertyNames(
  230. modelName,
  231. tableItem,
  232. ),
  233. );
  234. if (filter && typeof filter === 'object') {
  235. if (filter.where)
  236. modelItems = this.getService(WhereClauseTool).filter(
  237. modelItems,
  238. filter.where,
  239. );
  240. if (filter.skip || filter.limit)
  241. modelItems = this.getService(SliceClauseTool).slice(
  242. modelItems,
  243. filter.skip,
  244. filter.limit,
  245. );
  246. if (filter.order)
  247. this.getService(OrderClauseTool).sort(modelItems, filter.order);
  248. }
  249. return modelItems;
  250. }
  251. /**
  252. * Find by id.
  253. *
  254. * @param {string} modelName
  255. * @param {string|number} id
  256. * @param {object|undefined} filter
  257. * @returns {Promise<object>}
  258. */
  259. // eslint-disable-next-line no-unused-vars
  260. async findById(modelName, id, filter = undefined) {
  261. const table = this._getTableOrCreate(modelName);
  262. const tableData = table.get(id);
  263. const pkPropName =
  264. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  265. modelName,
  266. );
  267. if (!tableData)
  268. throw new InvalidArgumentError(
  269. 'The value %v of the primary key %v does not exist in the model %v.',
  270. id,
  271. pkPropName,
  272. modelName,
  273. );
  274. return this.getService(
  275. ModelDefinitionUtils,
  276. ).convertColumnNamesToPropertyNames(modelName, tableData);
  277. }
  278. /**
  279. * Delete.
  280. *
  281. * @param {string} modelName
  282. * @param {object|undefined} where
  283. * @returns {Promise<number>}
  284. */
  285. async delete(modelName, where = undefined) {
  286. const table = this._getTableOrCreate(modelName);
  287. const tableItems = Array.from(table.values());
  288. if (!tableItems.length) return 0;
  289. let modelItems = tableItems.map(tableItem =>
  290. this.getService(ModelDefinitionUtils).convertColumnNamesToPropertyNames(
  291. modelName,
  292. tableItem,
  293. ),
  294. );
  295. if (where && typeof where === 'object')
  296. modelItems = this.getService(WhereClauseTool).filter(modelItems, where);
  297. const size = modelItems.length;
  298. const idPropName =
  299. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  300. modelName,
  301. );
  302. modelItems.forEach(modelData => {
  303. const idValue = modelData[idPropName];
  304. table.delete(idValue);
  305. });
  306. return size;
  307. }
  308. /**
  309. * Delete by id.
  310. *
  311. * @param {string} modelName
  312. * @param {string|number} id
  313. * @returns {Promise<boolean>}
  314. */
  315. async deleteById(modelName, id) {
  316. const table = this._getTableOrCreate(modelName);
  317. const isExists = table.has(id);
  318. table.delete(id);
  319. return isExists;
  320. }
  321. /**
  322. * Exists.
  323. *
  324. * @param {string} modelName
  325. * @param {string|number} id
  326. * @returns {Promise<boolean>}
  327. */
  328. async exists(modelName, id) {
  329. const table = this._getTableOrCreate(modelName);
  330. return table.has(id);
  331. }
  332. /**
  333. * Count.
  334. *
  335. * @param {string} modelName
  336. * @param {object|undefined} where
  337. * @returns {Promise<number>}
  338. */
  339. async count(modelName, where = undefined) {
  340. const table = this._getTableOrCreate(modelName);
  341. const tableItems = Array.from(table.values());
  342. let modelItems = tableItems.map(tableItem =>
  343. this.getService(ModelDefinitionUtils).convertColumnNamesToPropertyNames(
  344. modelName,
  345. tableItem,
  346. ),
  347. );
  348. if (where && typeof where === 'object')
  349. modelItems = this.getService(WhereClauseTool).filter(modelItems, where);
  350. return modelItems.length;
  351. }
  352. }