ParseOp.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.UnsetOp = exports.SetOp = exports.RemoveOp = exports.RelationOp = exports.Op = exports.IncrementOp = exports.AddUniqueOp = exports.AddOp = void 0;
  6. exports.opFromJSON = opFromJSON;
  7. var _arrayContainsObject = _interopRequireDefault(require("./arrayContainsObject"));
  8. var _decode = _interopRequireDefault(require("./decode"));
  9. var _encode = _interopRequireDefault(require("./encode"));
  10. var _ParseObject = _interopRequireDefault(require("./ParseObject"));
  11. var _ParseRelation = _interopRequireDefault(require("./ParseRelation"));
  12. var _unique = _interopRequireDefault(require("./unique"));
  13. function _interopRequireDefault(obj) {
  14. return obj && obj.__esModule ? obj : {
  15. default: obj
  16. };
  17. }
  18. /**
  19. * Copyright (c) 2015-present, Parse, LLC.
  20. * All rights reserved.
  21. *
  22. * This source code is licensed under the BSD-style license found in the
  23. * LICENSE file in the root directory of this source tree. An additional grant
  24. * of patent rights can be found in the PATENTS file in the same directory.
  25. *
  26. * @flow
  27. */
  28. function opFromJSON(json
  29. /*: { [key: string]: any }*/
  30. )
  31. /*: ?Op*/
  32. {
  33. if (!json || !json.__op) {
  34. return null;
  35. }
  36. switch (json.__op) {
  37. case 'Delete':
  38. return new UnsetOp();
  39. case 'Increment':
  40. return new IncrementOp(json.amount);
  41. case 'Add':
  42. return new AddOp((0, _decode.default)(json.objects));
  43. case 'AddUnique':
  44. return new AddUniqueOp((0, _decode.default)(json.objects));
  45. case 'Remove':
  46. return new RemoveOp((0, _decode.default)(json.objects));
  47. case 'AddRelation':
  48. {
  49. const toAdd = (0, _decode.default)(json.objects);
  50. if (!Array.isArray(toAdd)) {
  51. return new RelationOp([], []);
  52. }
  53. return new RelationOp(toAdd, []);
  54. }
  55. case 'RemoveRelation':
  56. {
  57. const toRemove = (0, _decode.default)(json.objects);
  58. if (!Array.isArray(toRemove)) {
  59. return new RelationOp([], []);
  60. }
  61. return new RelationOp([], toRemove);
  62. }
  63. case 'Batch':
  64. {
  65. let toAdd = [];
  66. let toRemove = [];
  67. for (let i = 0; i < json.ops.length; i++) {
  68. if (json.ops[i].__op === 'AddRelation') {
  69. toAdd = toAdd.concat((0, _decode.default)(json.ops[i].objects));
  70. } else if (json.ops[i].__op === 'RemoveRelation') {
  71. toRemove = toRemove.concat((0, _decode.default)(json.ops[i].objects));
  72. }
  73. }
  74. return new RelationOp(toAdd, toRemove);
  75. }
  76. }
  77. return null;
  78. }
  79. class Op {
  80. // Empty parent class
  81. applyTo()
  82. /*: mixed*/
  83. {}
  84. /* eslint-disable-line no-unused-vars */
  85. mergeWith()
  86. /*: ?Op*/
  87. {}
  88. /* eslint-disable-line no-unused-vars */
  89. toJSON()
  90. /*: mixed*/
  91. {}
  92. }
  93. exports.Op = Op;
  94. class SetOp extends Op {
  95. /*:: _value: ?mixed;*/
  96. constructor(value
  97. /*: mixed*/
  98. ) {
  99. super();
  100. this._value = value;
  101. }
  102. applyTo()
  103. /*: mixed*/
  104. {
  105. return this._value;
  106. }
  107. mergeWith()
  108. /*: SetOp*/
  109. {
  110. return new SetOp(this._value);
  111. }
  112. toJSON(offline
  113. /*:: ?: boolean*/
  114. ) {
  115. return (0, _encode.default)(this._value, false, true, undefined, offline);
  116. }
  117. }
  118. exports.SetOp = SetOp;
  119. class UnsetOp extends Op {
  120. applyTo() {
  121. return undefined;
  122. }
  123. mergeWith()
  124. /*: UnsetOp*/
  125. {
  126. return new UnsetOp();
  127. }
  128. toJSON()
  129. /*: { __op: string }*/
  130. {
  131. return {
  132. __op: 'Delete'
  133. };
  134. }
  135. }
  136. exports.UnsetOp = UnsetOp;
  137. class IncrementOp extends Op {
  138. /*:: _amount: number;*/
  139. constructor(amount
  140. /*: number*/
  141. ) {
  142. super();
  143. if (typeof amount !== 'number') {
  144. throw new TypeError('Increment Op must be initialized with a numeric amount.');
  145. }
  146. this._amount = amount;
  147. }
  148. applyTo(value
  149. /*: ?mixed*/
  150. )
  151. /*: number*/
  152. {
  153. if (typeof value === 'undefined') {
  154. return this._amount;
  155. }
  156. if (typeof value !== 'number') {
  157. throw new TypeError('Cannot increment a non-numeric value.');
  158. }
  159. return this._amount + value;
  160. }
  161. mergeWith(previous
  162. /*: Op*/
  163. )
  164. /*: Op*/
  165. {
  166. if (!previous) {
  167. return this;
  168. }
  169. if (previous instanceof SetOp) {
  170. return new SetOp(this.applyTo(previous._value));
  171. }
  172. if (previous instanceof UnsetOp) {
  173. return new SetOp(this._amount);
  174. }
  175. if (previous instanceof IncrementOp) {
  176. return new IncrementOp(this.applyTo(previous._amount));
  177. }
  178. throw new Error('Cannot merge Increment Op with the previous Op');
  179. }
  180. toJSON()
  181. /*: { __op: string, amount: number }*/
  182. {
  183. return {
  184. __op: 'Increment',
  185. amount: this._amount
  186. };
  187. }
  188. }
  189. exports.IncrementOp = IncrementOp;
  190. class AddOp extends Op {
  191. /*:: _value: Array<mixed>;*/
  192. constructor(value
  193. /*: mixed | Array<mixed>*/
  194. ) {
  195. super();
  196. this._value = Array.isArray(value) ? value : [value];
  197. }
  198. applyTo(value
  199. /*: mixed*/
  200. )
  201. /*: Array<mixed>*/
  202. {
  203. if (value == null) {
  204. return this._value;
  205. }
  206. if (Array.isArray(value)) {
  207. return value.concat(this._value);
  208. }
  209. throw new Error('Cannot add elements to a non-array value');
  210. }
  211. mergeWith(previous
  212. /*: Op*/
  213. )
  214. /*: Op*/
  215. {
  216. if (!previous) {
  217. return this;
  218. }
  219. if (previous instanceof SetOp) {
  220. return new SetOp(this.applyTo(previous._value));
  221. }
  222. if (previous instanceof UnsetOp) {
  223. return new SetOp(this._value);
  224. }
  225. if (previous instanceof AddOp) {
  226. return new AddOp(this.applyTo(previous._value));
  227. }
  228. throw new Error('Cannot merge Add Op with the previous Op');
  229. }
  230. toJSON()
  231. /*: { __op: string, objects: mixed }*/
  232. {
  233. return {
  234. __op: 'Add',
  235. objects: (0, _encode.default)(this._value, false, true)
  236. };
  237. }
  238. }
  239. exports.AddOp = AddOp;
  240. class AddUniqueOp extends Op {
  241. /*:: _value: Array<mixed>;*/
  242. constructor(value
  243. /*: mixed | Array<mixed>*/
  244. ) {
  245. super();
  246. this._value = (0, _unique.default)(Array.isArray(value) ? value : [value]);
  247. }
  248. applyTo(value
  249. /*: mixed | Array<mixed>*/
  250. )
  251. /*: Array<mixed>*/
  252. {
  253. if (value == null) {
  254. return this._value || [];
  255. }
  256. if (Array.isArray(value)) {
  257. const toAdd = [];
  258. this._value.forEach(v => {
  259. if (v instanceof _ParseObject.default) {
  260. if (!(0, _arrayContainsObject.default)(value, v)) {
  261. toAdd.push(v);
  262. }
  263. } else {
  264. if (value.indexOf(v) < 0) {
  265. toAdd.push(v);
  266. }
  267. }
  268. });
  269. return value.concat(toAdd);
  270. }
  271. throw new Error('Cannot add elements to a non-array value');
  272. }
  273. mergeWith(previous
  274. /*: Op*/
  275. )
  276. /*: Op*/
  277. {
  278. if (!previous) {
  279. return this;
  280. }
  281. if (previous instanceof SetOp) {
  282. return new SetOp(this.applyTo(previous._value));
  283. }
  284. if (previous instanceof UnsetOp) {
  285. return new SetOp(this._value);
  286. }
  287. if (previous instanceof AddUniqueOp) {
  288. return new AddUniqueOp(this.applyTo(previous._value));
  289. }
  290. throw new Error('Cannot merge AddUnique Op with the previous Op');
  291. }
  292. toJSON()
  293. /*: { __op: string, objects: mixed }*/
  294. {
  295. return {
  296. __op: 'AddUnique',
  297. objects: (0, _encode.default)(this._value, false, true)
  298. };
  299. }
  300. }
  301. exports.AddUniqueOp = AddUniqueOp;
  302. class RemoveOp extends Op {
  303. /*:: _value: Array<mixed>;*/
  304. constructor(value
  305. /*: mixed | Array<mixed>*/
  306. ) {
  307. super();
  308. this._value = (0, _unique.default)(Array.isArray(value) ? value : [value]);
  309. }
  310. applyTo(value
  311. /*: mixed | Array<mixed>*/
  312. )
  313. /*: Array<mixed>*/
  314. {
  315. if (value == null) {
  316. return [];
  317. }
  318. if (Array.isArray(value)) {
  319. // var i = value.indexOf(this._value);
  320. const removed = value.concat([]);
  321. for (let i = 0; i < this._value.length; i++) {
  322. let index = removed.indexOf(this._value[i]);
  323. while (index > -1) {
  324. removed.splice(index, 1);
  325. index = removed.indexOf(this._value[i]);
  326. }
  327. if (this._value[i] instanceof _ParseObject.default && this._value[i].id) {
  328. for (let j = 0; j < removed.length; j++) {
  329. if (removed[j] instanceof _ParseObject.default && this._value[i].id === removed[j].id) {
  330. removed.splice(j, 1);
  331. j--;
  332. }
  333. }
  334. }
  335. }
  336. return removed;
  337. }
  338. throw new Error('Cannot remove elements from a non-array value');
  339. }
  340. mergeWith(previous
  341. /*: Op*/
  342. )
  343. /*: Op*/
  344. {
  345. if (!previous) {
  346. return this;
  347. }
  348. if (previous instanceof SetOp) {
  349. return new SetOp(this.applyTo(previous._value));
  350. }
  351. if (previous instanceof UnsetOp) {
  352. return new UnsetOp();
  353. }
  354. if (previous instanceof RemoveOp) {
  355. const uniques = previous._value.concat([]);
  356. for (let i = 0; i < this._value.length; i++) {
  357. if (this._value[i] instanceof _ParseObject.default) {
  358. if (!(0, _arrayContainsObject.default)(uniques, this._value[i])) {
  359. uniques.push(this._value[i]);
  360. }
  361. } else {
  362. if (uniques.indexOf(this._value[i]) < 0) {
  363. uniques.push(this._value[i]);
  364. }
  365. }
  366. }
  367. return new RemoveOp(uniques);
  368. }
  369. throw new Error('Cannot merge Remove Op with the previous Op');
  370. }
  371. toJSON()
  372. /*: { __op: string, objects: mixed }*/
  373. {
  374. return {
  375. __op: 'Remove',
  376. objects: (0, _encode.default)(this._value, false, true)
  377. };
  378. }
  379. }
  380. exports.RemoveOp = RemoveOp;
  381. class RelationOp extends Op {
  382. /*:: _targetClassName: ?string;*/
  383. /*:: relationsToAdd: Array<string>;*/
  384. /*:: relationsToRemove: Array<string>;*/
  385. constructor(adds
  386. /*: Array<ParseObject | string>*/
  387. , removes
  388. /*: Array<ParseObject | string>*/
  389. ) {
  390. super();
  391. this._targetClassName = null;
  392. if (Array.isArray(adds)) {
  393. this.relationsToAdd = (0, _unique.default)(adds.map(this._extractId, this));
  394. }
  395. if (Array.isArray(removes)) {
  396. this.relationsToRemove = (0, _unique.default)(removes.map(this._extractId, this));
  397. }
  398. }
  399. _extractId(obj
  400. /*: string | ParseObject*/
  401. )
  402. /*: string*/
  403. {
  404. if (typeof obj === 'string') {
  405. return obj;
  406. }
  407. if (!obj.id) {
  408. throw new Error('You cannot add or remove an unsaved Parse Object from a relation');
  409. }
  410. if (!this._targetClassName) {
  411. this._targetClassName = obj.className;
  412. }
  413. if (this._targetClassName !== obj.className) {
  414. throw new Error('Tried to create a Relation with 2 different object types: ' + this._targetClassName + ' and ' + obj.className + '.');
  415. }
  416. return obj.id;
  417. }
  418. applyTo(value
  419. /*: mixed*/
  420. , object
  421. /*:: ?: { className: string, id: ?string }*/
  422. , key
  423. /*:: ?: string*/
  424. )
  425. /*: ?ParseRelation*/
  426. {
  427. if (!value) {
  428. if (!object || !key) {
  429. throw new Error('Cannot apply a RelationOp without either a previous value, or an object and a key');
  430. }
  431. const parent = new _ParseObject.default(object.className);
  432. if (object.id && object.id.indexOf('local') === 0) {
  433. parent._localId = object.id;
  434. } else if (object.id) {
  435. parent.id = object.id;
  436. }
  437. const relation = new _ParseRelation.default(parent, key);
  438. relation.targetClassName = this._targetClassName;
  439. return relation;
  440. }
  441. if (value instanceof _ParseRelation.default) {
  442. if (this._targetClassName) {
  443. if (value.targetClassName) {
  444. if (this._targetClassName !== value.targetClassName) {
  445. throw new Error('Related object must be a ' + value.targetClassName + ', but a ' + this._targetClassName + ' was passed in.');
  446. }
  447. } else {
  448. value.targetClassName = this._targetClassName;
  449. }
  450. }
  451. return value;
  452. } else {
  453. throw new Error('Relation cannot be applied to a non-relation field');
  454. }
  455. }
  456. mergeWith(previous
  457. /*: Op*/
  458. )
  459. /*: Op*/
  460. {
  461. if (!previous) {
  462. return this;
  463. } else if (previous instanceof UnsetOp) {
  464. throw new Error('You cannot modify a relation after deleting it.');
  465. } else if (previous instanceof SetOp && previous._value instanceof _ParseRelation.default) {
  466. return this;
  467. } else if (previous instanceof RelationOp) {
  468. if (previous._targetClassName && previous._targetClassName !== this._targetClassName) {
  469. throw new Error('Related object must be of class ' + previous._targetClassName + ', but ' + (this._targetClassName || 'null') + ' was passed in.');
  470. }
  471. const newAdd = previous.relationsToAdd.concat([]);
  472. this.relationsToRemove.forEach(r => {
  473. const index = newAdd.indexOf(r);
  474. if (index > -1) {
  475. newAdd.splice(index, 1);
  476. }
  477. });
  478. this.relationsToAdd.forEach(r => {
  479. const index = newAdd.indexOf(r);
  480. if (index < 0) {
  481. newAdd.push(r);
  482. }
  483. });
  484. const newRemove = previous.relationsToRemove.concat([]);
  485. this.relationsToAdd.forEach(r => {
  486. const index = newRemove.indexOf(r);
  487. if (index > -1) {
  488. newRemove.splice(index, 1);
  489. }
  490. });
  491. this.relationsToRemove.forEach(r => {
  492. const index = newRemove.indexOf(r);
  493. if (index < 0) {
  494. newRemove.push(r);
  495. }
  496. });
  497. const newRelation = new RelationOp(newAdd, newRemove);
  498. newRelation._targetClassName = this._targetClassName;
  499. return newRelation;
  500. }
  501. throw new Error('Cannot merge Relation Op with the previous Op');
  502. }
  503. toJSON()
  504. /*: { __op?: string, objects?: mixed, ops?: mixed }*/
  505. {
  506. const idToPointer = id => {
  507. return {
  508. __type: 'Pointer',
  509. className: this._targetClassName,
  510. objectId: id
  511. };
  512. };
  513. let adds = null;
  514. let removes = null;
  515. let pointers = null;
  516. if (this.relationsToAdd.length > 0) {
  517. pointers = this.relationsToAdd.map(idToPointer);
  518. adds = {
  519. __op: 'AddRelation',
  520. objects: pointers
  521. };
  522. }
  523. if (this.relationsToRemove.length > 0) {
  524. pointers = this.relationsToRemove.map(idToPointer);
  525. removes = {
  526. __op: 'RemoveRelation',
  527. objects: pointers
  528. };
  529. }
  530. if (adds && removes) {
  531. return {
  532. __op: 'Batch',
  533. ops: [adds, removes]
  534. };
  535. }
  536. return adds || removes || {};
  537. }
  538. }
  539. exports.RelationOp = RelationOp;