cleverMerge.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. /** @type {WeakMap<EXPECTED_OBJECT, WeakMap<EXPECTED_OBJECT, EXPECTED_OBJECT>>} */
  7. const mergeCache = new WeakMap();
  8. /** @type {WeakMap<EXPECTED_OBJECT, Map<string, Map<string | number | boolean, EXPECTED_OBJECT>>>} */
  9. const setPropertyCache = new WeakMap();
  10. const DELETE = Symbol("DELETE");
  11. const DYNAMIC_INFO = Symbol("cleverMerge dynamic info");
  12. /**
  13. * Merges two given objects and caches the result to avoid computation if same objects passed as arguments again.
  14. * @template T
  15. * @template O
  16. * @example
  17. * // performs cleverMerge(first, second), stores the result in WeakMap and returns result
  18. * cachedCleverMerge({a: 1}, {a: 2})
  19. * {a: 2}
  20. * // when same arguments passed, gets the result from WeakMap and returns it.
  21. * cachedCleverMerge({a: 1}, {a: 2})
  22. * {a: 2}
  23. * @param {T | null | undefined} first first object
  24. * @param {O | null | undefined} second second object
  25. * @returns {T & O | T | O} merged object of first and second object
  26. */
  27. const cachedCleverMerge = (first, second) => {
  28. if (second === undefined) return /** @type {T} */ (first);
  29. if (first === undefined) return /** @type {O} */ (second);
  30. if (typeof second !== "object" || second === null)
  31. return /** @type {O} */ (second);
  32. if (typeof first !== "object" || first === null)
  33. return /** @type {T} */ (first);
  34. let innerCache = mergeCache.get(first);
  35. if (innerCache === undefined) {
  36. innerCache = new WeakMap();
  37. mergeCache.set(first, innerCache);
  38. }
  39. const prevMerge = /** @type {T & O} */ (innerCache.get(second));
  40. if (prevMerge !== undefined) return prevMerge;
  41. const newMerge = _cleverMerge(first, second, true);
  42. innerCache.set(second, newMerge);
  43. return newMerge;
  44. };
  45. /**
  46. * @template T
  47. * @param {Partial<T>} obj object
  48. * @param {string} property property
  49. * @param {string | number | boolean} value assignment value
  50. * @returns {T} new object
  51. */
  52. const cachedSetProperty = (obj, property, value) => {
  53. let mapByProperty = setPropertyCache.get(obj);
  54. if (mapByProperty === undefined) {
  55. mapByProperty = new Map();
  56. setPropertyCache.set(obj, mapByProperty);
  57. }
  58. let mapByValue = mapByProperty.get(property);
  59. if (mapByValue === undefined) {
  60. mapByValue = new Map();
  61. mapByProperty.set(property, mapByValue);
  62. }
  63. let result = mapByValue.get(value);
  64. if (result) return /** @type {T} */ (result);
  65. result = {
  66. ...obj,
  67. [property]: value
  68. };
  69. mapByValue.set(value, result);
  70. return /** @type {T} */ (result);
  71. };
  72. /**
  73. * @template V
  74. * @typedef {Map<string, V | undefined>} ByValues
  75. */
  76. /**
  77. * @typedef {object} ObjectParsedPropertyEntry
  78. * @property {TODO | undefined} base base value
  79. * @property {string | undefined} byProperty the name of the selector property
  80. * @property {ByValues<TODO>} byValues value depending on selector property, merged with base
  81. */
  82. /** @typedef {(function(...EXPECTED_ANY): object) & { [DYNAMIC_INFO]: [DynamicFunction, object] }} DynamicFunction */
  83. /**
  84. * @typedef {object} ParsedObject
  85. * @property {Map<string, ObjectParsedPropertyEntry>} static static properties (key is property name)
  86. * @property {{ byProperty: string, fn: DynamicFunction } | undefined} dynamic dynamic part
  87. */
  88. /** @type {WeakMap<EXPECTED_OBJECT, ParsedObject>} */
  89. const parseCache = new WeakMap();
  90. /**
  91. * @template {object} T
  92. * @param {T} obj the object
  93. * @returns {ParsedObject} parsed object
  94. */
  95. const cachedParseObject = obj => {
  96. const entry = parseCache.get(/** @type {EXPECTED_OBJECT} */ (obj));
  97. if (entry !== undefined) return entry;
  98. const result = parseObject(obj);
  99. parseCache.set(/** @type {EXPECTED_OBJECT} */ (obj), result);
  100. return result;
  101. };
  102. /**
  103. * @template {object} T
  104. * @template V
  105. * @param {T} obj the object
  106. * @returns {ParsedObject} parsed object
  107. */
  108. const parseObject = obj => {
  109. const info = new Map();
  110. let dynamicInfo;
  111. /**
  112. * @param {string} p path
  113. * @returns {Partial<ObjectParsedPropertyEntry>} object parsed property entry
  114. */
  115. const getInfo = p => {
  116. const entry = info.get(p);
  117. if (entry !== undefined) return entry;
  118. const newEntry = {
  119. base: undefined,
  120. byProperty: undefined,
  121. byValues: undefined
  122. };
  123. info.set(p, newEntry);
  124. return newEntry;
  125. };
  126. for (const key of Object.keys(obj)) {
  127. if (key.startsWith("by")) {
  128. const byProperty = /** @type {keyof T} */ (key);
  129. const byObj = /** @type {TODO} */ (obj[byProperty]);
  130. if (typeof byObj === "object") {
  131. for (const byValue of Object.keys(byObj)) {
  132. const obj = byObj[/** @type {keyof (keyof T)} */ (byValue)];
  133. for (const key of Object.keys(obj)) {
  134. const entry = getInfo(key);
  135. if (entry.byProperty === undefined) {
  136. entry.byProperty = /** @type {string} */ (byProperty);
  137. entry.byValues = new Map();
  138. } else if (entry.byProperty !== byProperty) {
  139. throw new Error(
  140. `${/** @type {string} */ (byProperty)} and ${entry.byProperty} for a single property is not supported`
  141. );
  142. }
  143. /** @type {ByValues<V>} */
  144. (entry.byValues).set(
  145. byValue,
  146. obj[/** @type {keyof (keyof T)} */ (key)]
  147. );
  148. if (byValue === "default") {
  149. for (const otherByValue of Object.keys(byObj)) {
  150. if (
  151. !(
  152. /** @type {ByValues<V>} */
  153. (entry.byValues).has(otherByValue)
  154. )
  155. )
  156. /** @type {ByValues<V>} */
  157. (entry.byValues).set(otherByValue, undefined);
  158. }
  159. }
  160. }
  161. }
  162. } else if (typeof byObj === "function") {
  163. if (dynamicInfo === undefined) {
  164. dynamicInfo = {
  165. byProperty: key,
  166. fn: byObj
  167. };
  168. } else {
  169. throw new Error(
  170. `${key} and ${dynamicInfo.byProperty} when both are functions is not supported`
  171. );
  172. }
  173. } else {
  174. const entry = getInfo(key);
  175. entry.base = obj[/** @type {keyof T} */ (key)];
  176. }
  177. } else {
  178. const entry = getInfo(key);
  179. entry.base = obj[/** @type {keyof T} */ (key)];
  180. }
  181. }
  182. return {
  183. static: info,
  184. dynamic: dynamicInfo
  185. };
  186. };
  187. /**
  188. * @template {object} T
  189. * @param {Map<string, ObjectParsedPropertyEntry>} info static properties (key is property name)
  190. * @param {{ byProperty: string, fn: (...args: EXPECTED_ANY[]) => T } | undefined} dynamicInfo dynamic part
  191. * @returns {T} the object
  192. */
  193. const serializeObject = (info, dynamicInfo) => {
  194. const obj = /** @type {T} */ ({});
  195. // Setup byProperty structure
  196. for (const entry of info.values()) {
  197. if (entry.byProperty !== undefined) {
  198. const byProperty = /** @type {keyof T} */ (entry.byProperty);
  199. const byObj = (obj[byProperty] =
  200. obj[byProperty] || /** @type {TODO} */ ({}));
  201. for (const byValue of entry.byValues.keys()) {
  202. byObj[byValue] = byObj[byValue] || {};
  203. }
  204. }
  205. }
  206. for (const [key, entry] of info) {
  207. if (entry.base !== undefined) {
  208. obj[/** @type {keyof T} */ (key)] = entry.base;
  209. }
  210. // Fill byProperty structure
  211. if (entry.byProperty !== undefined) {
  212. const byProperty = /** @type {keyof T} */ (entry.byProperty);
  213. const byObj = (obj[byProperty] =
  214. obj[byProperty] || /** @type {TODO} */ ({}));
  215. for (const byValue of Object.keys(byObj)) {
  216. const value = getFromByValues(entry.byValues, byValue);
  217. if (value !== undefined) byObj[byValue][key] = value;
  218. }
  219. }
  220. }
  221. if (dynamicInfo !== undefined) {
  222. /** @type {TODO} */
  223. (obj)[dynamicInfo.byProperty] = dynamicInfo.fn;
  224. }
  225. return obj;
  226. };
  227. const VALUE_TYPE_UNDEFINED = 0;
  228. const VALUE_TYPE_ATOM = 1;
  229. const VALUE_TYPE_ARRAY_EXTEND = 2;
  230. const VALUE_TYPE_OBJECT = 3;
  231. const VALUE_TYPE_DELETE = 4;
  232. /**
  233. * @template T
  234. * @param {T} value a single value
  235. * @returns {VALUE_TYPE_UNDEFINED | VALUE_TYPE_ATOM | VALUE_TYPE_ARRAY_EXTEND | VALUE_TYPE_OBJECT | VALUE_TYPE_DELETE} value type
  236. */
  237. const getValueType = value => {
  238. if (value === undefined) {
  239. return VALUE_TYPE_UNDEFINED;
  240. } else if (value === DELETE) {
  241. return VALUE_TYPE_DELETE;
  242. } else if (Array.isArray(value)) {
  243. if (value.includes("...")) return VALUE_TYPE_ARRAY_EXTEND;
  244. return VALUE_TYPE_ATOM;
  245. } else if (
  246. typeof value === "object" &&
  247. value !== null &&
  248. (!value.constructor || value.constructor === Object)
  249. ) {
  250. return VALUE_TYPE_OBJECT;
  251. }
  252. return VALUE_TYPE_ATOM;
  253. };
  254. /**
  255. * Merges two objects. Objects are deeply clever merged.
  256. * Arrays might reference the old value with "...".
  257. * Non-object values take preference over object values.
  258. * @template T
  259. * @template O
  260. * @param {T} first first object
  261. * @param {O} second second object
  262. * @returns {T & O | T | O} merged object of first and second object
  263. */
  264. const cleverMerge = (first, second) => {
  265. if (second === undefined) return first;
  266. if (first === undefined) return second;
  267. if (typeof second !== "object" || second === null) return second;
  268. if (typeof first !== "object" || first === null) return first;
  269. return /** @type {T & O} */ (_cleverMerge(first, second, false));
  270. };
  271. /**
  272. * @template {object} T
  273. * @template {object} O
  274. * Merges two objects. Objects are deeply clever merged.
  275. * @param {T} first first
  276. * @param {O} second second
  277. * @param {boolean} internalCaching should parsing of objects and nested merges be cached
  278. * @returns {T & O} merged object of first and second object
  279. */
  280. const _cleverMerge = (first, second, internalCaching = false) => {
  281. const firstObject = internalCaching
  282. ? cachedParseObject(first)
  283. : parseObject(first);
  284. const { static: firstInfo, dynamic: firstDynamicInfo } = firstObject;
  285. // If the first argument has a dynamic part we modify the dynamic part to merge the second argument
  286. if (firstDynamicInfo !== undefined) {
  287. let { byProperty, fn } = firstDynamicInfo;
  288. const fnInfo = fn[DYNAMIC_INFO];
  289. if (fnInfo) {
  290. second =
  291. /** @type {TODO} */
  292. (
  293. internalCaching
  294. ? cachedCleverMerge(fnInfo[1], second)
  295. : cleverMerge(fnInfo[1], second)
  296. );
  297. fn = fnInfo[0];
  298. }
  299. /** @type {DynamicFunction} */
  300. const newFn = (...args) => {
  301. const fnResult = fn(...args);
  302. return internalCaching
  303. ? cachedCleverMerge(fnResult, second)
  304. : cleverMerge(fnResult, second);
  305. };
  306. newFn[DYNAMIC_INFO] = [fn, second];
  307. return /** @type {T & O} */ (
  308. serializeObject(firstObject.static, { byProperty, fn: newFn })
  309. );
  310. }
  311. // If the first part is static only, we merge the static parts and keep the dynamic part of the second argument
  312. const secondObject = internalCaching
  313. ? cachedParseObject(second)
  314. : parseObject(second);
  315. const { static: secondInfo, dynamic: secondDynamicInfo } = secondObject;
  316. /** @type {Map<string, ObjectParsedPropertyEntry>} */
  317. const resultInfo = new Map();
  318. for (const [key, firstEntry] of firstInfo) {
  319. const secondEntry = secondInfo.get(key);
  320. const entry =
  321. secondEntry !== undefined
  322. ? mergeEntries(firstEntry, secondEntry, internalCaching)
  323. : firstEntry;
  324. resultInfo.set(key, entry);
  325. }
  326. for (const [key, secondEntry] of secondInfo) {
  327. if (!firstInfo.has(key)) {
  328. resultInfo.set(key, secondEntry);
  329. }
  330. }
  331. return /** @type {T & O} */ (serializeObject(resultInfo, secondDynamicInfo));
  332. };
  333. /**
  334. * @param {ObjectParsedPropertyEntry} firstEntry a
  335. * @param {ObjectParsedPropertyEntry} secondEntry b
  336. * @param {boolean} internalCaching should parsing of objects and nested merges be cached
  337. * @returns {ObjectParsedPropertyEntry} new entry
  338. */
  339. const mergeEntries = (firstEntry, secondEntry, internalCaching) => {
  340. switch (getValueType(secondEntry.base)) {
  341. case VALUE_TYPE_ATOM:
  342. case VALUE_TYPE_DELETE:
  343. // No need to consider firstEntry at all
  344. // second value override everything
  345. // = second.base + second.byProperty
  346. return secondEntry;
  347. case VALUE_TYPE_UNDEFINED:
  348. if (!firstEntry.byProperty) {
  349. // = first.base + second.byProperty
  350. return {
  351. base: firstEntry.base,
  352. byProperty: secondEntry.byProperty,
  353. byValues: secondEntry.byValues
  354. };
  355. } else if (firstEntry.byProperty !== secondEntry.byProperty) {
  356. throw new Error(
  357. `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
  358. );
  359. } else {
  360. // = first.base + (first.byProperty + second.byProperty)
  361. // need to merge first and second byValues
  362. const newByValues = new Map(firstEntry.byValues);
  363. for (const [key, value] of secondEntry.byValues) {
  364. const firstValue = getFromByValues(firstEntry.byValues, key);
  365. newByValues.set(
  366. key,
  367. mergeSingleValue(firstValue, value, internalCaching)
  368. );
  369. }
  370. return {
  371. base: firstEntry.base,
  372. byProperty: firstEntry.byProperty,
  373. byValues: newByValues
  374. };
  375. }
  376. default: {
  377. if (!firstEntry.byProperty) {
  378. // The simple case
  379. // = (first.base + second.base) + second.byProperty
  380. return {
  381. base: mergeSingleValue(
  382. firstEntry.base,
  383. secondEntry.base,
  384. internalCaching
  385. ),
  386. byProperty: secondEntry.byProperty,
  387. byValues: secondEntry.byValues
  388. };
  389. }
  390. let newBase;
  391. const intermediateByValues = new Map(firstEntry.byValues);
  392. for (const [key, value] of intermediateByValues) {
  393. intermediateByValues.set(
  394. key,
  395. mergeSingleValue(value, secondEntry.base, internalCaching)
  396. );
  397. }
  398. if (
  399. Array.from(firstEntry.byValues.values()).every(value => {
  400. const type = getValueType(value);
  401. return type === VALUE_TYPE_ATOM || type === VALUE_TYPE_DELETE;
  402. })
  403. ) {
  404. // = (first.base + second.base) + ((first.byProperty + second.base) + second.byProperty)
  405. newBase = mergeSingleValue(
  406. firstEntry.base,
  407. secondEntry.base,
  408. internalCaching
  409. );
  410. } else {
  411. // = first.base + ((first.byProperty (+default) + second.base) + second.byProperty)
  412. newBase = firstEntry.base;
  413. if (!intermediateByValues.has("default"))
  414. intermediateByValues.set("default", secondEntry.base);
  415. }
  416. if (!secondEntry.byProperty) {
  417. // = first.base + (first.byProperty + second.base)
  418. return {
  419. base: newBase,
  420. byProperty: firstEntry.byProperty,
  421. byValues: intermediateByValues
  422. };
  423. } else if (firstEntry.byProperty !== secondEntry.byProperty) {
  424. throw new Error(
  425. `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
  426. );
  427. }
  428. const newByValues = new Map(intermediateByValues);
  429. for (const [key, value] of secondEntry.byValues) {
  430. const firstValue = getFromByValues(intermediateByValues, key);
  431. newByValues.set(
  432. key,
  433. mergeSingleValue(firstValue, value, internalCaching)
  434. );
  435. }
  436. return {
  437. base: newBase,
  438. byProperty: firstEntry.byProperty,
  439. byValues: newByValues
  440. };
  441. }
  442. }
  443. };
  444. /**
  445. * @template V
  446. * @param {ByValues<V>} byValues all values
  447. * @param {string} key value of the selector
  448. * @returns {V | undefined} value
  449. */
  450. const getFromByValues = (byValues, key) => {
  451. if (key !== "default" && byValues.has(key)) {
  452. return byValues.get(key);
  453. }
  454. return byValues.get("default");
  455. };
  456. /**
  457. * @template A
  458. * @template B
  459. * @param {A | A[]} a value
  460. * @param {B | B[]} b value
  461. * @param {boolean} internalCaching should parsing of objects and nested merges be cached
  462. * @returns {A & B | (A | B)[] | A | A[] | B | B[]} value
  463. */
  464. const mergeSingleValue = (a, b, internalCaching) => {
  465. const bType = getValueType(b);
  466. const aType = getValueType(a);
  467. switch (bType) {
  468. case VALUE_TYPE_DELETE:
  469. case VALUE_TYPE_ATOM:
  470. return b;
  471. case VALUE_TYPE_OBJECT: {
  472. return aType !== VALUE_TYPE_OBJECT
  473. ? b
  474. : internalCaching
  475. ? cachedCleverMerge(a, b)
  476. : cleverMerge(a, b);
  477. }
  478. case VALUE_TYPE_UNDEFINED:
  479. return a;
  480. case VALUE_TYPE_ARRAY_EXTEND:
  481. switch (
  482. aType !== VALUE_TYPE_ATOM
  483. ? aType
  484. : Array.isArray(a)
  485. ? VALUE_TYPE_ARRAY_EXTEND
  486. : VALUE_TYPE_OBJECT
  487. ) {
  488. case VALUE_TYPE_UNDEFINED:
  489. return b;
  490. case VALUE_TYPE_DELETE:
  491. return /** @type {B[]} */ (b).filter(item => item !== "...");
  492. case VALUE_TYPE_ARRAY_EXTEND: {
  493. /** @type {(A | B)[]} */
  494. const newArray = [];
  495. for (const item of /** @type {B[]} */ (b)) {
  496. if (item === "...") {
  497. for (const item of /** @type {A[]} */ (a)) {
  498. newArray.push(item);
  499. }
  500. } else {
  501. newArray.push(item);
  502. }
  503. }
  504. return newArray;
  505. }
  506. case VALUE_TYPE_OBJECT:
  507. return /** @type {(A | B)[]} */ (b).map(item =>
  508. item === "..." ? /** @type {A} */ (a) : item
  509. );
  510. default:
  511. throw new Error("Not implemented");
  512. }
  513. default:
  514. throw new Error("Not implemented");
  515. }
  516. };
  517. /**
  518. * @template {object} T
  519. * @param {T} obj the object
  520. * @param {(keyof T)[]=} keysToKeepOriginalValue keys to keep original value
  521. * @returns {T} the object without operations like "..." or DELETE
  522. */
  523. const removeOperations = (obj, keysToKeepOriginalValue = []) => {
  524. const newObj = /** @type {T} */ ({});
  525. for (const _key of Object.keys(obj)) {
  526. const key = /** @type {keyof T} */ (_key);
  527. const value = obj[key];
  528. const type = getValueType(value);
  529. if (type === VALUE_TYPE_OBJECT && keysToKeepOriginalValue.includes(key)) {
  530. newObj[key] = value;
  531. continue;
  532. }
  533. switch (type) {
  534. case VALUE_TYPE_UNDEFINED:
  535. case VALUE_TYPE_DELETE:
  536. break;
  537. case VALUE_TYPE_OBJECT:
  538. newObj[key] =
  539. /** @type {T[keyof T]} */
  540. (
  541. removeOperations(
  542. /** @type {T} */
  543. (value),
  544. keysToKeepOriginalValue
  545. )
  546. );
  547. break;
  548. case VALUE_TYPE_ARRAY_EXTEND:
  549. newObj[key] =
  550. /** @type {T[keyof T]} */
  551. (
  552. /** @type {EXPECTED_ANY[]} */
  553. (value).filter(i => i !== "...")
  554. );
  555. break;
  556. default:
  557. newObj[key] = value;
  558. break;
  559. }
  560. }
  561. return newObj;
  562. };
  563. /**
  564. * @template T
  565. * @template {keyof T} P
  566. * @template V
  567. * @param {T} obj the object
  568. * @param {P} byProperty the by description
  569. * @param {...V} values values
  570. * @returns {Omit<T, P>} object with merged byProperty
  571. */
  572. const resolveByProperty = (obj, byProperty, ...values) => {
  573. if (typeof obj !== "object" || obj === null || !(byProperty in obj)) {
  574. return obj;
  575. }
  576. const { [byProperty]: _byValue, ..._remaining } = obj;
  577. const remaining = /** @type {T} */ (_remaining);
  578. const byValue =
  579. /** @type {Record<string, T> | ((...args: V[]) => T)} */
  580. (_byValue);
  581. if (typeof byValue === "object") {
  582. const key = /** @type {string} */ (values[0]);
  583. if (key in byValue) {
  584. return cachedCleverMerge(remaining, byValue[key]);
  585. } else if ("default" in byValue) {
  586. return cachedCleverMerge(remaining, byValue.default);
  587. }
  588. return remaining;
  589. } else if (typeof byValue === "function") {
  590. // eslint-disable-next-line prefer-spread
  591. const result = byValue.apply(null, values);
  592. return cachedCleverMerge(
  593. remaining,
  594. resolveByProperty(result, byProperty, ...values)
  595. );
  596. }
  597. return obj;
  598. };
  599. module.exports.cachedSetProperty = cachedSetProperty;
  600. module.exports.cachedCleverMerge = cachedCleverMerge;
  601. module.exports.cleverMerge = cleverMerge;
  602. module.exports.resolveByProperty = resolveByProperty;
  603. module.exports.removeOperations = removeOperations;
  604. module.exports.DELETE = DELETE;