ParseObject.js 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779
  1. /**
  2. * Copyright (c) 2015-present, Parse, LLC.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. * @flow
  10. */
  11. import CoreManager from './CoreManager';
  12. import canBeSerialized from './canBeSerialized';
  13. import decode from './decode';
  14. import encode from './encode';
  15. import escape from './escape';
  16. import ParseACL from './ParseACL';
  17. import parseDate from './parseDate';
  18. import ParseError from './ParseError';
  19. import ParseFile from './ParseFile';
  20. import { when, continueWhile } from './promiseUtils';
  21. import { DEFAULT_PIN, PIN_PREFIX } from './LocalDatastoreUtils';
  22. import { opFromJSON, Op, SetOp, UnsetOp, IncrementOp, AddOp, AddUniqueOp, RemoveOp, RelationOp } from './ParseOp';
  23. import ParseQuery from './ParseQuery';
  24. import ParseRelation from './ParseRelation';
  25. import * as SingleInstanceStateController from './SingleInstanceStateController';
  26. import unique from './unique';
  27. import * as UniqueInstanceStateController from './UniqueInstanceStateController';
  28. import unsavedChildren from './unsavedChildren';
  29. /*:: import type { AttributeMap, OpsMap } from './ObjectStateMutations';*/
  30. /*:: import type { RequestOptions, FullOptions } from './RESTController';*/
  31. /*:: export type Pointer = {
  32. __type: string;
  33. className: string;
  34. objectId: string;
  35. };*/
  36. /*:: type SaveParams = {
  37. method: string;
  38. path: string;
  39. body: AttributeMap;
  40. };*/
  41. const DEFAULT_BATCH_SIZE = 20; // Mapping of class names to constructors, so we can populate objects from the
  42. // server with appropriate subclasses of ParseObject
  43. const classMap = {}; // Global counter for generating unique local Ids
  44. let localCount = 0; // Global counter for generating unique Ids for non-single-instance objects
  45. let objectCount = 0; // On web clients, objects are single-instance: any two objects with the same Id
  46. // will have the same attributes. However, this may be dangerous default
  47. // behavior in a server scenario
  48. let singleInstance = !CoreManager.get('IS_NODE');
  49. if (singleInstance) {
  50. CoreManager.setObjectStateController(SingleInstanceStateController);
  51. } else {
  52. CoreManager.setObjectStateController(UniqueInstanceStateController);
  53. }
  54. function getServerUrlPath() {
  55. let serverUrl = CoreManager.get('SERVER_URL');
  56. if (serverUrl[serverUrl.length - 1] !== '/') {
  57. serverUrl += '/';
  58. }
  59. const url = serverUrl.replace(/https?:\/\//, '');
  60. return url.substr(url.indexOf('/'));
  61. }
  62. /**
  63. * Creates a new model with defined attributes.
  64. *
  65. * <p>You won't normally call this method directly. It is recommended that
  66. * you use a subclass of <code>Parse.Object</code> instead, created by calling
  67. * <code>extend</code>.</p>
  68. *
  69. * <p>However, if you don't want to use a subclass, or aren't sure which
  70. * subclass is appropriate, you can use this form:<pre>
  71. * var object = new Parse.Object("ClassName");
  72. * </pre>
  73. * That is basically equivalent to:<pre>
  74. * var MyClass = Parse.Object.extend("ClassName");
  75. * var object = new MyClass();
  76. * </pre></p>
  77. *
  78. * @alias Parse.Object
  79. */
  80. class ParseObject {
  81. /**
  82. * @param {String} className The class name for the object
  83. * @param {Object} attributes The initial set of data to store in the object.
  84. * @param {Object} options The options for this object instance.
  85. */
  86. constructor(className
  87. /*: ?string | { className: string, [attr: string]: mixed }*/
  88. , attributes
  89. /*:: ?: { [attr: string]: mixed }*/
  90. , options
  91. /*:: ?: { ignoreValidation: boolean }*/
  92. ) {
  93. // Enable legacy initializers
  94. if (typeof this.initialize === 'function') {
  95. this.initialize.apply(this, arguments);
  96. }
  97. let toSet = null;
  98. this._objCount = objectCount++;
  99. if (typeof className === 'string') {
  100. this.className = className;
  101. if (attributes && typeof attributes === 'object') {
  102. toSet = attributes;
  103. }
  104. } else if (className && typeof className === 'object') {
  105. this.className = className.className;
  106. toSet = {};
  107. for (const attr in className) {
  108. if (attr !== 'className') {
  109. toSet[attr] = className[attr];
  110. }
  111. }
  112. if (attributes && typeof attributes === 'object') {
  113. options = attributes;
  114. }
  115. }
  116. if (toSet && !this.set(toSet, options)) {
  117. throw new Error('Can\'t create an invalid Parse Object');
  118. }
  119. }
  120. /**
  121. * The ID of this object, unique within its class.
  122. * @property id
  123. * @type String
  124. */
  125. /*:: id: ?string;*/
  126. /*:: _localId: ?string;*/
  127. /*:: _objCount: number;*/
  128. /*:: className: string;*/
  129. /** Prototype getters / setters **/
  130. get attributes()
  131. /*: AttributeMap*/
  132. {
  133. const stateController = CoreManager.getObjectStateController();
  134. return Object.freeze(stateController.estimateAttributes(this._getStateIdentifier()));
  135. }
  136. /**
  137. * The first time this object was saved on the server.
  138. * @property createdAt
  139. * @type Date
  140. */
  141. get createdAt()
  142. /*: ?Date*/
  143. {
  144. return this._getServerData().createdAt;
  145. }
  146. /**
  147. * The last time this object was updated on the server.
  148. * @property updatedAt
  149. * @type Date
  150. */
  151. get updatedAt()
  152. /*: ?Date*/
  153. {
  154. return this._getServerData().updatedAt;
  155. }
  156. /** Private methods **/
  157. /**
  158. * Returns a local or server Id used uniquely identify this object
  159. */
  160. _getId()
  161. /*: string*/
  162. {
  163. if (typeof this.id === 'string') {
  164. return this.id;
  165. }
  166. if (typeof this._localId === 'string') {
  167. return this._localId;
  168. }
  169. const localId = 'local' + String(localCount++);
  170. this._localId = localId;
  171. return localId;
  172. }
  173. /**
  174. * Returns a unique identifier used to pull data from the State Controller.
  175. */
  176. _getStateIdentifier()
  177. /*: ParseObject | {id: string, className: string}*/
  178. {
  179. if (singleInstance) {
  180. let id = this.id;
  181. if (!id) {
  182. id = this._getId();
  183. }
  184. return {
  185. id: id,
  186. className: this.className
  187. };
  188. } else {
  189. return this;
  190. }
  191. }
  192. _getServerData()
  193. /*: AttributeMap*/
  194. {
  195. const stateController = CoreManager.getObjectStateController();
  196. return stateController.getServerData(this._getStateIdentifier());
  197. }
  198. _clearServerData() {
  199. const serverData = this._getServerData();
  200. const unset = {};
  201. for (const attr in serverData) {
  202. unset[attr] = undefined;
  203. }
  204. const stateController = CoreManager.getObjectStateController();
  205. stateController.setServerData(this._getStateIdentifier(), unset);
  206. }
  207. _getPendingOps()
  208. /*: Array<OpsMap>*/
  209. {
  210. const stateController = CoreManager.getObjectStateController();
  211. return stateController.getPendingOps(this._getStateIdentifier());
  212. }
  213. /**
  214. * @param {Array<string>} [keysToClear] - if specified, only ops matching
  215. * these fields will be cleared
  216. */
  217. _clearPendingOps(keysToClear
  218. /*:: ?: Array<string>*/
  219. ) {
  220. const pending = this._getPendingOps();
  221. const latest = pending[pending.length - 1];
  222. const keys = keysToClear || Object.keys(latest);
  223. keys.forEach(key => {
  224. delete latest[key];
  225. });
  226. }
  227. _getDirtyObjectAttributes()
  228. /*: AttributeMap*/
  229. {
  230. const attributes = this.attributes;
  231. const stateController = CoreManager.getObjectStateController();
  232. const objectCache = stateController.getObjectCache(this._getStateIdentifier());
  233. const dirty = {};
  234. for (const attr in attributes) {
  235. const val = attributes[attr];
  236. if (val && typeof val === 'object' && !(val instanceof ParseObject) && !(val instanceof ParseFile) && !(val instanceof ParseRelation)) {
  237. // Due to the way browsers construct maps, the key order will not change
  238. // unless the object is changed
  239. try {
  240. const json = encode(val, false, true);
  241. const stringified = JSON.stringify(json);
  242. if (objectCache[attr] !== stringified) {
  243. dirty[attr] = val;
  244. }
  245. } catch (e) {
  246. // Error occurred, possibly by a nested unsaved pointer in a mutable container
  247. // No matter how it happened, it indicates a change in the attribute
  248. dirty[attr] = val;
  249. }
  250. }
  251. }
  252. return dirty;
  253. }
  254. _toFullJSON(seen
  255. /*:: ?: Array<any>*/
  256. )
  257. /*: AttributeMap*/
  258. {
  259. const json
  260. /*: { [key: string]: mixed }*/
  261. = this.toJSON(seen);
  262. json.__type = 'Object';
  263. json.className = this.className;
  264. return json;
  265. }
  266. _getSaveJSON()
  267. /*: AttributeMap*/
  268. {
  269. const pending = this._getPendingOps();
  270. const dirtyObjects = this._getDirtyObjectAttributes();
  271. const json = {};
  272. for (var attr in dirtyObjects) {
  273. let isDotNotation = false;
  274. for (let i = 0; i < pending.length; i += 1) {
  275. for (const field in pending[i]) {
  276. // Dot notation operations are handled later
  277. if (field.includes('.')) {
  278. const fieldName = field.split('.')[0];
  279. if (fieldName === attr) {
  280. isDotNotation = true;
  281. break;
  282. }
  283. }
  284. }
  285. }
  286. if (!isDotNotation) {
  287. json[attr] = new SetOp(dirtyObjects[attr]).toJSON();
  288. }
  289. }
  290. for (attr in pending[0]) {
  291. json[attr] = pending[0][attr].toJSON();
  292. }
  293. return json;
  294. }
  295. _getSaveParams()
  296. /*: SaveParams*/
  297. {
  298. const method = this.id ? 'PUT' : 'POST';
  299. const body = this._getSaveJSON();
  300. let path = 'classes/' + this.className;
  301. if (this.id) {
  302. path += '/' + this.id;
  303. } else if (this.className === '_User') {
  304. path = 'users';
  305. }
  306. return {
  307. method,
  308. body,
  309. path
  310. };
  311. }
  312. _finishFetch(serverData
  313. /*: AttributeMap*/
  314. ) {
  315. if (!this.id && serverData.objectId) {
  316. this.id = serverData.objectId;
  317. }
  318. const stateController = CoreManager.getObjectStateController();
  319. stateController.initializeState(this._getStateIdentifier());
  320. const decoded = {};
  321. for (const attr in serverData) {
  322. if (attr === 'ACL') {
  323. decoded[attr] = new ParseACL(serverData[attr]);
  324. } else if (attr !== 'objectId') {
  325. decoded[attr] = decode(serverData[attr]);
  326. if (decoded[attr] instanceof ParseRelation) {
  327. decoded[attr]._ensureParentAndKey(this, attr);
  328. }
  329. }
  330. }
  331. if (decoded.createdAt && typeof decoded.createdAt === 'string') {
  332. decoded.createdAt = parseDate(decoded.createdAt);
  333. }
  334. if (decoded.updatedAt && typeof decoded.updatedAt === 'string') {
  335. decoded.updatedAt = parseDate(decoded.updatedAt);
  336. }
  337. if (!decoded.updatedAt && decoded.createdAt) {
  338. decoded.updatedAt = decoded.createdAt;
  339. }
  340. stateController.commitServerChanges(this._getStateIdentifier(), decoded);
  341. }
  342. _setExisted(existed
  343. /*: boolean*/
  344. ) {
  345. const stateController = CoreManager.getObjectStateController();
  346. const state = stateController.getState(this._getStateIdentifier());
  347. if (state) {
  348. state.existed = existed;
  349. }
  350. }
  351. _migrateId(serverId
  352. /*: string*/
  353. ) {
  354. if (this._localId && serverId) {
  355. if (singleInstance) {
  356. const stateController = CoreManager.getObjectStateController();
  357. const oldState = stateController.removeState(this._getStateIdentifier());
  358. this.id = serverId;
  359. delete this._localId;
  360. if (oldState) {
  361. stateController.initializeState(this._getStateIdentifier(), oldState);
  362. }
  363. } else {
  364. this.id = serverId;
  365. delete this._localId;
  366. }
  367. }
  368. }
  369. _handleSaveResponse(response
  370. /*: AttributeMap*/
  371. , status
  372. /*: number*/
  373. ) {
  374. const changes = {};
  375. const stateController = CoreManager.getObjectStateController();
  376. const pending = stateController.popPendingState(this._getStateIdentifier());
  377. for (var attr in pending) {
  378. if (pending[attr] instanceof RelationOp) {
  379. changes[attr] = pending[attr].applyTo(undefined, this, attr);
  380. } else if (!(attr in response)) {
  381. // Only SetOps and UnsetOps should not come back with results
  382. changes[attr] = pending[attr].applyTo(undefined);
  383. }
  384. }
  385. for (attr in response) {
  386. if ((attr === 'createdAt' || attr === 'updatedAt') && typeof response[attr] === 'string') {
  387. changes[attr] = parseDate(response[attr]);
  388. } else if (attr === 'ACL') {
  389. changes[attr] = new ParseACL(response[attr]);
  390. } else if (attr !== 'objectId') {
  391. changes[attr] = decode(response[attr]);
  392. if (changes[attr] instanceof UnsetOp) {
  393. changes[attr] = undefined;
  394. }
  395. }
  396. }
  397. if (changes.createdAt && !changes.updatedAt) {
  398. changes.updatedAt = changes.createdAt;
  399. }
  400. this._migrateId(response.objectId);
  401. if (status !== 201) {
  402. this._setExisted(true);
  403. }
  404. stateController.commitServerChanges(this._getStateIdentifier(), changes);
  405. }
  406. _handleSaveError() {
  407. const stateController = CoreManager.getObjectStateController();
  408. stateController.mergeFirstPendingState(this._getStateIdentifier());
  409. }
  410. /** Public methods **/
  411. initialize() {} // NOOP
  412. /**
  413. * Returns a JSON version of the object suitable for saving to Parse.
  414. * @return {Object}
  415. */
  416. toJSON(seen
  417. /*: Array<any> | void*/
  418. )
  419. /*: AttributeMap*/
  420. {
  421. const seenEntry = this.id ? this.className + ':' + this.id : this;
  422. seen = seen || [seenEntry];
  423. const json = {};
  424. const attrs = this.attributes;
  425. for (const attr in attrs) {
  426. if ((attr === 'createdAt' || attr === 'updatedAt') && attrs[attr].toJSON) {
  427. json[attr] = attrs[attr].toJSON();
  428. } else {
  429. json[attr] = encode(attrs[attr], false, false, seen);
  430. }
  431. }
  432. const pending = this._getPendingOps();
  433. for (const attr in pending[0]) {
  434. json[attr] = pending[0][attr].toJSON();
  435. }
  436. if (this.id) {
  437. json.objectId = this.id;
  438. }
  439. return json;
  440. }
  441. /**
  442. * Determines whether this ParseObject is equal to another ParseObject
  443. * @param {Object} other - An other object ot compare
  444. * @return {Boolean}
  445. */
  446. equals(other
  447. /*: mixed*/
  448. )
  449. /*: boolean*/
  450. {
  451. if (this === other) {
  452. return true;
  453. }
  454. return other instanceof ParseObject && this.className === other.className && this.id === other.id && typeof this.id !== 'undefined';
  455. }
  456. /**
  457. * Returns true if this object has been modified since its last
  458. * save/refresh. If an attribute is specified, it returns true only if that
  459. * particular attribute has been modified since the last save/refresh.
  460. * @param {String} attr An attribute name (optional).
  461. * @return {Boolean}
  462. */
  463. dirty(attr
  464. /*:: ?: string*/
  465. )
  466. /*: boolean*/
  467. {
  468. if (!this.id) {
  469. return true;
  470. }
  471. const pendingOps = this._getPendingOps();
  472. const dirtyObjects = this._getDirtyObjectAttributes();
  473. if (attr) {
  474. if (dirtyObjects.hasOwnProperty(attr)) {
  475. return true;
  476. }
  477. for (let i = 0; i < pendingOps.length; i++) {
  478. if (pendingOps[i].hasOwnProperty(attr)) {
  479. return true;
  480. }
  481. }
  482. return false;
  483. }
  484. if (Object.keys(pendingOps[0]).length !== 0) {
  485. return true;
  486. }
  487. if (Object.keys(dirtyObjects).length !== 0) {
  488. return true;
  489. }
  490. return false;
  491. }
  492. /**
  493. * Returns an array of keys that have been modified since last save/refresh
  494. * @return {String[]}
  495. */
  496. dirtyKeys()
  497. /*: Array<string>*/
  498. {
  499. const pendingOps = this._getPendingOps();
  500. const keys = {};
  501. for (let i = 0; i < pendingOps.length; i++) {
  502. for (const attr in pendingOps[i]) {
  503. keys[attr] = true;
  504. }
  505. }
  506. const dirtyObjects = this._getDirtyObjectAttributes();
  507. for (const attr in dirtyObjects) {
  508. keys[attr] = true;
  509. }
  510. return Object.keys(keys);
  511. }
  512. /**
  513. * Returns true if the object has been fetched.
  514. * @return {Boolean}
  515. */
  516. isDataAvailable()
  517. /*: boolean*/
  518. {
  519. const serverData = this._getServerData();
  520. return !!Object.keys(serverData).length;
  521. }
  522. /**
  523. * Gets a Pointer referencing this Object.
  524. * @return {Pointer}
  525. */
  526. toPointer()
  527. /*: Pointer*/
  528. {
  529. if (!this.id) {
  530. throw new Error('Cannot create a pointer to an unsaved ParseObject');
  531. }
  532. return {
  533. __type: 'Pointer',
  534. className: this.className,
  535. objectId: this.id
  536. };
  537. }
  538. /**
  539. * Gets the value of an attribute.
  540. * @param {String} attr The string name of an attribute.
  541. */
  542. get(attr
  543. /*: string*/
  544. )
  545. /*: mixed*/
  546. {
  547. return this.attributes[attr];
  548. }
  549. /**
  550. * Gets a relation on the given class for the attribute.
  551. * @param String attr The attribute to get the relation for.
  552. * @return {Parse.Relation}
  553. */
  554. relation(attr
  555. /*: string*/
  556. )
  557. /*: ParseRelation*/
  558. {
  559. const value = this.get(attr);
  560. if (value) {
  561. if (!(value instanceof ParseRelation)) {
  562. throw new Error('Called relation() on non-relation field ' + attr);
  563. }
  564. value._ensureParentAndKey(this, attr);
  565. return value;
  566. }
  567. return new ParseRelation(this, attr);
  568. }
  569. /**
  570. * Gets the HTML-escaped value of an attribute.
  571. * @param {String} attr The string name of an attribute.
  572. */
  573. escape(attr
  574. /*: string*/
  575. )
  576. /*: string*/
  577. {
  578. let val = this.attributes[attr];
  579. if (val == null) {
  580. return '';
  581. }
  582. if (typeof val !== 'string') {
  583. if (typeof val.toString !== 'function') {
  584. return '';
  585. }
  586. val = val.toString();
  587. }
  588. return escape(val);
  589. }
  590. /**
  591. * Returns <code>true</code> if the attribute contains a value that is not
  592. * null or undefined.
  593. * @param {String} attr The string name of the attribute.
  594. * @return {Boolean}
  595. */
  596. has(attr
  597. /*: string*/
  598. )
  599. /*: boolean*/
  600. {
  601. const attributes = this.attributes;
  602. if (attributes.hasOwnProperty(attr)) {
  603. return attributes[attr] != null;
  604. }
  605. return false;
  606. }
  607. /**
  608. * Sets a hash of model attributes on the object.
  609. *
  610. * <p>You can call it with an object containing keys and values, with one
  611. * key and value, or dot notation. For example:<pre>
  612. * gameTurn.set({
  613. * player: player1,
  614. * diceRoll: 2
  615. * }, {
  616. * error: function(gameTurnAgain, error) {
  617. * // The set failed validation.
  618. * }
  619. * });
  620. *
  621. * game.set("currentPlayer", player2, {
  622. * error: function(gameTurnAgain, error) {
  623. * // The set failed validation.
  624. * }
  625. * });
  626. *
  627. * game.set("finished", true);</pre></p>
  628. *
  629. * game.set("player.score", 10);</pre></p>
  630. *
  631. * @param {String} key The key to set.
  632. * @param {} value The value to give it.
  633. * @param {Object} options A set of options for the set.
  634. * The only supported option is <code>error</code>.
  635. * @return {(ParseObject|Boolean)} true if the set succeeded.
  636. */
  637. set(key
  638. /*: mixed*/
  639. , value
  640. /*: mixed*/
  641. , options
  642. /*:: ?: mixed*/
  643. )
  644. /*: ParseObject | boolean*/
  645. {
  646. let changes = {};
  647. const newOps = {};
  648. if (key && typeof key === 'object') {
  649. changes = key;
  650. options = value;
  651. } else if (typeof key === 'string') {
  652. changes[key] = value;
  653. } else {
  654. return this;
  655. }
  656. options = options || {};
  657. let readonly = [];
  658. if (typeof this.constructor.readOnlyAttributes === 'function') {
  659. readonly = readonly.concat(this.constructor.readOnlyAttributes());
  660. }
  661. for (const k in changes) {
  662. if (k === 'createdAt' || k === 'updatedAt') {
  663. // This property is read-only, but for legacy reasons we silently
  664. // ignore it
  665. continue;
  666. }
  667. if (readonly.indexOf(k) > -1) {
  668. throw new Error('Cannot modify readonly attribute: ' + k);
  669. }
  670. if (options.unset) {
  671. newOps[k] = new UnsetOp();
  672. } else if (changes[k] instanceof Op) {
  673. newOps[k] = changes[k];
  674. } else if (changes[k] && typeof changes[k] === 'object' && typeof changes[k].__op === 'string') {
  675. newOps[k] = opFromJSON(changes[k]);
  676. } else if (k === 'objectId' || k === 'id') {
  677. if (typeof changes[k] === 'string') {
  678. this.id = changes[k];
  679. }
  680. } else if (k === 'ACL' && typeof changes[k] === 'object' && !(changes[k] instanceof ParseACL)) {
  681. newOps[k] = new SetOp(new ParseACL(changes[k]));
  682. } else if (changes[k] instanceof ParseRelation) {
  683. const relation = new ParseRelation(this, k);
  684. relation.targetClassName = changes[k].targetClassName;
  685. newOps[k] = new SetOp(relation);
  686. } else {
  687. newOps[k] = new SetOp(changes[k]);
  688. }
  689. }
  690. const currentAttributes = this.attributes; // Only set nested fields if exists
  691. const serverData = this._getServerData();
  692. if (typeof key === 'string' && key.includes('.')) {
  693. const field = key.split('.')[0];
  694. if (!serverData[field]) {
  695. return this;
  696. }
  697. } // Calculate new values
  698. const newValues = {};
  699. for (const attr in newOps) {
  700. if (newOps[attr] instanceof RelationOp) {
  701. newValues[attr] = newOps[attr].applyTo(currentAttributes[attr], this, attr);
  702. } else if (!(newOps[attr] instanceof UnsetOp)) {
  703. newValues[attr] = newOps[attr].applyTo(currentAttributes[attr]);
  704. }
  705. } // Validate changes
  706. if (!options.ignoreValidation) {
  707. const validation = this.validate(newValues);
  708. if (validation) {
  709. if (typeof options.error === 'function') {
  710. options.error(this, validation);
  711. }
  712. return false;
  713. }
  714. } // Consolidate Ops
  715. const pendingOps = this._getPendingOps();
  716. const last = pendingOps.length - 1;
  717. const stateController = CoreManager.getObjectStateController();
  718. for (const attr in newOps) {
  719. const nextOp = newOps[attr].mergeWith(pendingOps[last][attr]);
  720. stateController.setPendingOp(this._getStateIdentifier(), attr, nextOp);
  721. }
  722. return this;
  723. }
  724. /**
  725. * Remove an attribute from the model. This is a noop if the attribute doesn't
  726. * exist.
  727. * @param {String} attr The string name of an attribute.
  728. * @return {(ParseObject|Boolean)}
  729. */
  730. unset(attr
  731. /*: string*/
  732. , options
  733. /*:: ?: { [opt: string]: mixed }*/
  734. )
  735. /*: ParseObject | boolean*/
  736. {
  737. options = options || {};
  738. options.unset = true;
  739. return this.set(attr, null, options);
  740. }
  741. /**
  742. * Atomically increments the value of the given attribute the next time the
  743. * object is saved. If no amount is specified, 1 is used by default.
  744. *
  745. * @param attr {String} The key.
  746. * @param amount {Number} The amount to increment by (optional).
  747. * @return {(ParseObject|Boolean)}
  748. */
  749. increment(attr
  750. /*: string*/
  751. , amount
  752. /*:: ?: number*/
  753. )
  754. /*: ParseObject | boolean*/
  755. {
  756. if (typeof amount === 'undefined') {
  757. amount = 1;
  758. }
  759. if (typeof amount !== 'number') {
  760. throw new Error('Cannot increment by a non-numeric amount.');
  761. }
  762. return this.set(attr, new IncrementOp(amount));
  763. }
  764. /**
  765. * Atomically add an object to the end of the array associated with a given
  766. * key.
  767. * @param attr {String} The key.
  768. * @param item {} The item to add.
  769. * @return {(ParseObject|Boolean)}
  770. */
  771. add(attr
  772. /*: string*/
  773. , item
  774. /*: mixed*/
  775. )
  776. /*: ParseObject | boolean*/
  777. {
  778. return this.set(attr, new AddOp([item]));
  779. }
  780. /**
  781. * Atomically add the objects to the end of the array associated with a given
  782. * key.
  783. * @param attr {String} The key.
  784. * @param items {Object[]} The items to add.
  785. * @return {(ParseObject|Boolean)}
  786. */
  787. addAll(attr
  788. /*: string*/
  789. , items
  790. /*: Array<mixed>*/
  791. )
  792. /*: ParseObject | boolean*/
  793. {
  794. return this.set(attr, new AddOp(items));
  795. }
  796. /**
  797. * Atomically add an object to the array associated with a given key, only
  798. * if it is not already present in the array. The position of the insert is
  799. * not guaranteed.
  800. *
  801. * @param attr {String} The key.
  802. * @param item {} The object to add.
  803. * @return {(ParseObject|Boolean)}
  804. */
  805. addUnique(attr
  806. /*: string*/
  807. , item
  808. /*: mixed*/
  809. )
  810. /*: ParseObject | boolean*/
  811. {
  812. return this.set(attr, new AddUniqueOp([item]));
  813. }
  814. /**
  815. * Atomically add the objects to the array associated with a given key, only
  816. * if it is not already present in the array. The position of the insert is
  817. * not guaranteed.
  818. *
  819. * @param attr {String} The key.
  820. * @param items {Object[]} The objects to add.
  821. * @return {(ParseObject|Boolean)}
  822. */
  823. addAllUnique(attr
  824. /*: string*/
  825. , items
  826. /*: Array<mixed>*/
  827. )
  828. /*: ParseObject | boolean*/
  829. {
  830. return this.set(attr, new AddUniqueOp(items));
  831. }
  832. /**
  833. * Atomically remove all instances of an object from the array associated
  834. * with a given key.
  835. *
  836. * @param attr {String} The key.
  837. * @param item {} The object to remove.
  838. * @return {(ParseObject|Boolean)}
  839. */
  840. remove(attr
  841. /*: string*/
  842. , item
  843. /*: mixed*/
  844. )
  845. /*: ParseObject | boolean*/
  846. {
  847. return this.set(attr, new RemoveOp([item]));
  848. }
  849. /**
  850. * Atomically remove all instances of the objects from the array associated
  851. * with a given key.
  852. *
  853. * @param attr {String} The key.
  854. * @param items {Object[]} The object to remove.
  855. * @return {(ParseObject|Boolean)}
  856. */
  857. removeAll(attr
  858. /*: string*/
  859. , items
  860. /*: Array<mixed>*/
  861. )
  862. /*: ParseObject | boolean*/
  863. {
  864. return this.set(attr, new RemoveOp(items));
  865. }
  866. /**
  867. * Returns an instance of a subclass of Parse.Op describing what kind of
  868. * modification has been performed on this field since the last time it was
  869. * saved. For example, after calling object.increment("x"), calling
  870. * object.op("x") would return an instance of Parse.Op.Increment.
  871. *
  872. * @param attr {String} The key.
  873. * @returns {Parse.Op} The operation, or undefined if none.
  874. */
  875. op(attr
  876. /*: string*/
  877. )
  878. /*: ?Op*/
  879. {
  880. const pending = this._getPendingOps();
  881. for (let i = pending.length; i--;) {
  882. if (pending[i][attr]) {
  883. return pending[i][attr];
  884. }
  885. }
  886. }
  887. /**
  888. * Creates a new model with identical attributes to this one.
  889. * @return {Parse.Object}
  890. */
  891. clone()
  892. /*: any*/
  893. {
  894. const clone = new this.constructor();
  895. if (!clone.className) {
  896. clone.className = this.className;
  897. }
  898. let attributes = this.attributes;
  899. if (typeof this.constructor.readOnlyAttributes === 'function') {
  900. const readonly = this.constructor.readOnlyAttributes() || []; // Attributes are frozen, so we have to rebuild an object,
  901. // rather than delete readonly keys
  902. const copy = {};
  903. for (const a in attributes) {
  904. if (readonly.indexOf(a) < 0) {
  905. copy[a] = attributes[a];
  906. }
  907. }
  908. attributes = copy;
  909. }
  910. if (clone.set) {
  911. clone.set(attributes);
  912. }
  913. return clone;
  914. }
  915. /**
  916. * Creates a new instance of this object. Not to be confused with clone()
  917. * @return {Parse.Object}
  918. */
  919. newInstance()
  920. /*: any*/
  921. {
  922. const clone = new this.constructor();
  923. if (!clone.className) {
  924. clone.className = this.className;
  925. }
  926. clone.id = this.id;
  927. if (singleInstance) {
  928. // Just return an object with the right id
  929. return clone;
  930. }
  931. const stateController = CoreManager.getObjectStateController();
  932. if (stateController) {
  933. stateController.duplicateState(this._getStateIdentifier(), clone._getStateIdentifier());
  934. }
  935. return clone;
  936. }
  937. /**
  938. * Returns true if this object has never been saved to Parse.
  939. * @return {Boolean}
  940. */
  941. isNew()
  942. /*: boolean*/
  943. {
  944. return !this.id;
  945. }
  946. /**
  947. * Returns true if this object was created by the Parse server when the
  948. * object might have already been there (e.g. in the case of a Facebook
  949. * login)
  950. * @return {Boolean}
  951. */
  952. existed()
  953. /*: boolean*/
  954. {
  955. if (!this.id) {
  956. return false;
  957. }
  958. const stateController = CoreManager.getObjectStateController();
  959. const state = stateController.getState(this._getStateIdentifier());
  960. if (state) {
  961. return state.existed;
  962. }
  963. return false;
  964. }
  965. /**
  966. * Checks if the model is currently in a valid state.
  967. * @return {Boolean}
  968. */
  969. isValid()
  970. /*: boolean*/
  971. {
  972. return !this.validate(this.attributes);
  973. }
  974. /**
  975. * You should not call this function directly unless you subclass
  976. * <code>Parse.Object</code>, in which case you can override this method
  977. * to provide additional validation on <code>set</code> and
  978. * <code>save</code>. Your implementation should return
  979. *
  980. * @param {Object} attrs The current data to validate.
  981. * @return {} False if the data is valid. An error object otherwise.
  982. * @see Parse.Object#set
  983. */
  984. validate(attrs
  985. /*: AttributeMap*/
  986. )
  987. /*: ParseError | boolean*/
  988. {
  989. if (attrs.hasOwnProperty('ACL') && !(attrs.ACL instanceof ParseACL)) {
  990. return new ParseError(ParseError.OTHER_CAUSE, 'ACL must be a Parse ACL.');
  991. }
  992. for (const key in attrs) {
  993. if (!/^[A-Za-z][0-9A-Za-z_.]*$/.test(key)) {
  994. return new ParseError(ParseError.INVALID_KEY_NAME);
  995. }
  996. }
  997. return false;
  998. }
  999. /**
  1000. * Returns the ACL for this object.
  1001. * @returns {Parse.ACL} An instance of Parse.ACL.
  1002. * @see Parse.Object#get
  1003. */
  1004. getACL()
  1005. /*: ?ParseACL*/
  1006. {
  1007. const acl = this.get('ACL');
  1008. if (acl instanceof ParseACL) {
  1009. return acl;
  1010. }
  1011. return null;
  1012. }
  1013. /**
  1014. * Sets the ACL to be used for this object.
  1015. * @param {Parse.ACL} acl An instance of Parse.ACL.
  1016. * @param {Object} options
  1017. * @return {(ParseObject|Boolean)} Whether the set passed validation.
  1018. * @see Parse.Object#set
  1019. */
  1020. setACL(acl
  1021. /*: ParseACL*/
  1022. , options
  1023. /*:: ?: mixed*/
  1024. )
  1025. /*: ParseObject | boolean*/
  1026. {
  1027. return this.set('ACL', acl, options);
  1028. }
  1029. /**
  1030. * Clears any (or specific) changes to this object made since the last call to save()
  1031. * @param {string} [keys] - specify which fields to revert
  1032. */
  1033. revert(...keys)
  1034. /*: void*/
  1035. {
  1036. let keysToRevert;
  1037. if (keys.length) {
  1038. keysToRevert = [];
  1039. for (const key of keys) {
  1040. if (typeof key === "string") {
  1041. keysToRevert.push(key);
  1042. } else {
  1043. throw new Error("Parse.Object#revert expects either no, or a list of string, arguments.");
  1044. }
  1045. }
  1046. }
  1047. this._clearPendingOps(keysToRevert);
  1048. }
  1049. /**
  1050. * Clears all attributes on a model
  1051. * @return {(ParseObject | boolean)}
  1052. */
  1053. clear()
  1054. /*: ParseObject | boolean*/
  1055. {
  1056. const attributes = this.attributes;
  1057. const erasable = {};
  1058. let readonly = ['createdAt', 'updatedAt'];
  1059. if (typeof this.constructor.readOnlyAttributes === 'function') {
  1060. readonly = readonly.concat(this.constructor.readOnlyAttributes());
  1061. }
  1062. for (const attr in attributes) {
  1063. if (readonly.indexOf(attr) < 0) {
  1064. erasable[attr] = true;
  1065. }
  1066. }
  1067. return this.set(erasable, {
  1068. unset: true
  1069. });
  1070. }
  1071. /**
  1072. * Fetch the model from the server. If the server's representation of the
  1073. * model differs from its current attributes, they will be overriden.
  1074. *
  1075. * @param {Object} options
  1076. * Valid options are:<ul>
  1077. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  1078. * be used for this request.
  1079. * <li>sessionToken: A valid session token, used for making a request on
  1080. * behalf of a specific user.
  1081. * <li>include: The name(s) of the key(s) to include. Can be a string, an array of strings,
  1082. * or an array of array of strings.
  1083. * </ul>
  1084. * @return {Promise} A promise that is fulfilled when the fetch
  1085. * completes.
  1086. */
  1087. fetch(options
  1088. /*: RequestOptions*/
  1089. )
  1090. /*: Promise*/
  1091. {
  1092. options = options || {};
  1093. const fetchOptions = {};
  1094. if (options.hasOwnProperty('useMasterKey')) {
  1095. fetchOptions.useMasterKey = options.useMasterKey;
  1096. }
  1097. if (options.hasOwnProperty('sessionToken')) {
  1098. fetchOptions.sessionToken = options.sessionToken;
  1099. }
  1100. if (options.hasOwnProperty('include')) {
  1101. fetchOptions.include = [];
  1102. if (Array.isArray(options.include)) {
  1103. options.include.forEach(key => {
  1104. if (Array.isArray(key)) {
  1105. fetchOptions.include = fetchOptions.include.concat(key);
  1106. } else {
  1107. fetchOptions.include.push(key);
  1108. }
  1109. });
  1110. } else {
  1111. fetchOptions.include.push(options.include);
  1112. }
  1113. }
  1114. const controller = CoreManager.getObjectController();
  1115. return controller.fetch(this, true, fetchOptions);
  1116. }
  1117. /**
  1118. * Fetch the model from the server. If the server's representation of the
  1119. * model differs from its current attributes, they will be overriden.
  1120. *
  1121. * Includes nested Parse.Objects for the provided key. You can use dot
  1122. * notation to specify which fields in the included object are also fetched.
  1123. *
  1124. * @param {String|Array<string|Array<string>>} keys The name(s) of the key(s) to include.
  1125. * @param {Object} options
  1126. * Valid options are:<ul>
  1127. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  1128. * be used for this request.
  1129. * <li>sessionToken: A valid session token, used for making a request on
  1130. * behalf of a specific user.
  1131. * </ul>
  1132. * @return {Promise} A promise that is fulfilled when the fetch
  1133. * completes.
  1134. */
  1135. fetchWithInclude(keys
  1136. /*: String|Array<string|Array<string>>*/
  1137. , options
  1138. /*: RequestOptions*/
  1139. )
  1140. /*: Promise*/
  1141. {
  1142. options = options || {};
  1143. options.include = keys;
  1144. return this.fetch(options);
  1145. }
  1146. /**
  1147. * Set a hash of model attributes, and save the model to the server.
  1148. * updatedAt will be updated when the request returns.
  1149. * You can either call it as:<pre>
  1150. * object.save();</pre>
  1151. * or<pre>
  1152. * object.save(attrs);</pre>
  1153. * or<pre>
  1154. * object.save(null, options);</pre>
  1155. * or<pre>
  1156. * object.save(attrs, options);</pre>
  1157. * or<pre>
  1158. * object.save(key, value, options);</pre>
  1159. *
  1160. * For example, <pre>
  1161. * gameTurn.save({
  1162. * player: "Jake Cutter",
  1163. * diceRoll: 2
  1164. * }).then(function(gameTurnAgain) {
  1165. * // The save was successful.
  1166. * }, function(error) {
  1167. * // The save failed. Error is an instance of Parse.Error.
  1168. * });</pre>
  1169. *
  1170. * @param {String|Object|null} [attrs]
  1171. * Valid options are:<ul>
  1172. * <li>`Object` - Key/value pairs to update on the object.</li>
  1173. * <li>`String` Key - Key of attribute to update (requires arg2 to also be string)</li>
  1174. * <li>`null` - Passing null for arg1 allows you to save the object with options passed in arg2.</li>
  1175. * </ul>
  1176. *
  1177. * @param {String|Object} [options]
  1178. * <ul>
  1179. * <li>`String` Value - If arg1 was passed as a key, arg2 is the value that should be set on that key.</li>
  1180. * <li>`Object` Options - Valid options are:
  1181. * <ul>
  1182. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  1183. * be used for this request.
  1184. * <li>sessionToken: A valid session token, used for making a request on
  1185. * behalf of a specific user.
  1186. * </ul>
  1187. * </li>
  1188. * </ul>
  1189. *
  1190. * @param {Object} [options]
  1191. * Used to pass option parameters to method if arg1 and arg2 were both passed as strings.
  1192. * Valid options are:
  1193. * <ul>
  1194. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  1195. * be used for this request.
  1196. * <li>sessionToken: A valid session token, used for making a request on
  1197. * behalf of a specific user.
  1198. * </ul>
  1199. *
  1200. * @return {Promise} A promise that is fulfilled when the save
  1201. * completes.
  1202. */
  1203. save(arg1
  1204. /*: ?string | { [attr: string]: mixed }*/
  1205. , arg2
  1206. /*: FullOptions | mixed*/
  1207. , arg3
  1208. /*:: ?: FullOptions*/
  1209. )
  1210. /*: Promise*/
  1211. {
  1212. let attrs;
  1213. let options;
  1214. if (typeof arg1 === 'object' || typeof arg1 === 'undefined') {
  1215. attrs = arg1;
  1216. if (typeof arg2 === 'object') {
  1217. options = arg2;
  1218. }
  1219. } else {
  1220. attrs = {};
  1221. attrs[arg1] = arg2;
  1222. options = arg3;
  1223. } // TODO: safely remove me
  1224. // Support save({ success: function() {}, error: function() {} })
  1225. if (!options && attrs) {
  1226. options = {};
  1227. if (typeof attrs.success === 'function') {
  1228. options.success = attrs.success;
  1229. delete attrs.success;
  1230. }
  1231. if (typeof attrs.error === 'function') {
  1232. options.error = attrs.error;
  1233. delete attrs.error;
  1234. }
  1235. }
  1236. if (attrs) {
  1237. const validation = this.validate(attrs);
  1238. if (validation) {
  1239. if (options && typeof options.error === 'function') {
  1240. options.error(this, validation);
  1241. }
  1242. return Promise.reject(validation);
  1243. }
  1244. this.set(attrs, options);
  1245. }
  1246. options = options || {};
  1247. const saveOptions = {};
  1248. if (options.hasOwnProperty('useMasterKey')) {
  1249. saveOptions.useMasterKey = !!options.useMasterKey;
  1250. }
  1251. if (options.hasOwnProperty('sessionToken') && typeof options.sessionToken === 'string') {
  1252. saveOptions.sessionToken = options.sessionToken;
  1253. }
  1254. const controller = CoreManager.getObjectController();
  1255. const unsaved = unsavedChildren(this);
  1256. return controller.save(unsaved, saveOptions).then(() => {
  1257. return controller.save(this, saveOptions);
  1258. });
  1259. }
  1260. /**
  1261. * Destroy this model on the server if it was already persisted.
  1262. *
  1263. * @param {Object} options
  1264. * Valid options are:<ul>
  1265. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  1266. * be used for this request.
  1267. * <li>sessionToken: A valid session token, used for making a request on
  1268. * behalf of a specific user.
  1269. * </ul>
  1270. * @return {Promise} A promise that is fulfilled when the destroy
  1271. * completes.
  1272. */
  1273. destroy(options
  1274. /*: RequestOptions*/
  1275. )
  1276. /*: Promise*/
  1277. {
  1278. options = options || {};
  1279. const destroyOptions = {};
  1280. if (options.hasOwnProperty('useMasterKey')) {
  1281. destroyOptions.useMasterKey = options.useMasterKey;
  1282. }
  1283. if (options.hasOwnProperty('sessionToken')) {
  1284. destroyOptions.sessionToken = options.sessionToken;
  1285. }
  1286. if (!this.id) {
  1287. return Promise.resolve();
  1288. }
  1289. return CoreManager.getObjectController().destroy(this, destroyOptions);
  1290. }
  1291. /**
  1292. * Asynchronously stores the object and every object it points to in the local datastore,
  1293. * recursively, using a default pin name: _default.
  1294. *
  1295. * If those other objects have not been fetched from Parse, they will not be stored.
  1296. * However, if they have changed data, all the changes will be retained.
  1297. *
  1298. * <pre>
  1299. * await object.pin();
  1300. * </pre>
  1301. *
  1302. * To retrieve object:
  1303. * <code>query.fromLocalDatastore()</code> or <code>query.fromPin()</code>
  1304. *
  1305. * @return {Promise} A promise that is fulfilled when the pin completes.
  1306. */
  1307. pin()
  1308. /*: Promise<void>*/
  1309. {
  1310. return ParseObject.pinAllWithName(DEFAULT_PIN, [this]);
  1311. }
  1312. /**
  1313. * Asynchronously removes the object and every object it points to in the local datastore,
  1314. * recursively, using a default pin name: _default.
  1315. *
  1316. * <pre>
  1317. * await object.unPin();
  1318. * </pre>
  1319. *
  1320. * @return {Promise} A promise that is fulfilled when the unPin completes.
  1321. */
  1322. unPin()
  1323. /*: Promise<void>*/
  1324. {
  1325. return ParseObject.unPinAllWithName(DEFAULT_PIN, [this]);
  1326. }
  1327. /**
  1328. * Asynchronously returns if the object is pinned
  1329. *
  1330. * <pre>
  1331. * const isPinned = await object.isPinned();
  1332. * </pre>
  1333. *
  1334. * @return {Promise<boolean>} A boolean promise that is fulfilled if object is pinned.
  1335. */
  1336. async isPinned()
  1337. /*: Promise<boolean>*/
  1338. {
  1339. const localDatastore = CoreManager.getLocalDatastore();
  1340. if (!localDatastore.isEnabled) {
  1341. return Promise.reject('Parse.enableLocalDatastore() must be called first');
  1342. }
  1343. const objectKey = localDatastore.getKeyForObject(this);
  1344. const pin = await localDatastore.fromPinWithName(objectKey);
  1345. return pin.length > 0;
  1346. }
  1347. /**
  1348. * Asynchronously stores the objects and every object they point to in the local datastore, recursively.
  1349. *
  1350. * If those other objects have not been fetched from Parse, they will not be stored.
  1351. * However, if they have changed data, all the changes will be retained.
  1352. *
  1353. * <pre>
  1354. * await object.pinWithName(name);
  1355. * </pre>
  1356. *
  1357. * To retrieve object:
  1358. * <code>query.fromLocalDatastore()</code> or <code>query.fromPinWithName(name)</code>
  1359. *
  1360. * @param {String} name Name of Pin.
  1361. * @return {Promise} A promise that is fulfilled when the pin completes.
  1362. */
  1363. pinWithName(name
  1364. /*: string*/
  1365. )
  1366. /*: Promise<void>*/
  1367. {
  1368. return ParseObject.pinAllWithName(name, [this]);
  1369. }
  1370. /**
  1371. * Asynchronously removes the object and every object it points to in the local datastore, recursively.
  1372. *
  1373. * <pre>
  1374. * await object.unPinWithName(name);
  1375. * </pre>
  1376. *
  1377. * @param {String} name Name of Pin.
  1378. * @return {Promise} A promise that is fulfilled when the unPin completes.
  1379. */
  1380. unPinWithName(name
  1381. /*: string*/
  1382. )
  1383. /*: Promise<void>*/
  1384. {
  1385. return ParseObject.unPinAllWithName(name, [this]);
  1386. }
  1387. /**
  1388. * Asynchronously loads data from the local datastore into this object.
  1389. *
  1390. * <pre>
  1391. * await object.fetchFromLocalDatastore();
  1392. * </pre>
  1393. *
  1394. * You can create an unfetched pointer with <code>Parse.Object.createWithoutData()</code>
  1395. * and then call <code>fetchFromLocalDatastore()</code> on it.
  1396. *
  1397. * @return {Promise} A promise that is fulfilled when the fetch completes.
  1398. */
  1399. async fetchFromLocalDatastore()
  1400. /*: Promise<ParseObject>*/
  1401. {
  1402. const localDatastore = CoreManager.getLocalDatastore();
  1403. if (!localDatastore.isEnabled) {
  1404. throw new Error('Parse.enableLocalDatastore() must be called first');
  1405. }
  1406. const objectKey = localDatastore.getKeyForObject(this);
  1407. const pinned = await localDatastore._serializeObject(objectKey);
  1408. if (!pinned) {
  1409. throw new Error('Cannot fetch an unsaved ParseObject');
  1410. }
  1411. const result = ParseObject.fromJSON(pinned);
  1412. this._finishFetch(result.toJSON());
  1413. return this;
  1414. }
  1415. /** Static methods **/
  1416. static _clearAllState() {
  1417. const stateController = CoreManager.getObjectStateController();
  1418. stateController.clearAllState();
  1419. }
  1420. /**
  1421. * Fetches the given list of Parse.Object.
  1422. * If any error is encountered, stops and calls the error handler.
  1423. *
  1424. * <pre>
  1425. * Parse.Object.fetchAll([object1, object2, ...])
  1426. * .then((list) => {
  1427. * // All the objects were fetched.
  1428. * }, (error) => {
  1429. * // An error occurred while fetching one of the objects.
  1430. * });
  1431. * </pre>
  1432. *
  1433. * @param {Array} list A list of <code>Parse.Object</code>.
  1434. * @param {Object} options
  1435. * Valid options are:<ul>
  1436. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  1437. * be used for this request.
  1438. * <li>sessionToken: A valid session token, used for making a request on
  1439. * behalf of a specific user.
  1440. * <li>include: The name(s) of the key(s) to include. Can be a string, an array of strings,
  1441. * or an array of array of strings.
  1442. * </ul>
  1443. * @static
  1444. */
  1445. static fetchAll(list
  1446. /*: Array<ParseObject>*/
  1447. , options
  1448. /*: RequestOptions*/
  1449. = {}) {
  1450. const queryOptions = {};
  1451. if (options.hasOwnProperty('useMasterKey')) {
  1452. queryOptions.useMasterKey = options.useMasterKey;
  1453. }
  1454. if (options.hasOwnProperty('sessionToken')) {
  1455. queryOptions.sessionToken = options.sessionToken;
  1456. }
  1457. if (options.hasOwnProperty('include')) {
  1458. queryOptions.include = [];
  1459. if (Array.isArray(options.include)) {
  1460. options.include.forEach(key => {
  1461. if (Array.isArray(key)) {
  1462. queryOptions.include = queryOptions.include.concat(key);
  1463. } else {
  1464. queryOptions.include.push(key);
  1465. }
  1466. });
  1467. } else {
  1468. queryOptions.include.push(options.include);
  1469. }
  1470. }
  1471. return CoreManager.getObjectController().fetch(list, true, queryOptions);
  1472. }
  1473. /**
  1474. * Fetches the given list of Parse.Object.
  1475. *
  1476. * Includes nested Parse.Objects for the provided key. You can use dot
  1477. * notation to specify which fields in the included object are also fetched.
  1478. *
  1479. * If any error is encountered, stops and calls the error handler.
  1480. *
  1481. * <pre>
  1482. * Parse.Object.fetchAllWithInclude([object1, object2, ...], [pointer1, pointer2, ...])
  1483. * .then((list) => {
  1484. * // All the objects were fetched.
  1485. * }, (error) => {
  1486. * // An error occurred while fetching one of the objects.
  1487. * });
  1488. * </pre>
  1489. *
  1490. * @param {Array} list A list of <code>Parse.Object</code>.
  1491. * @param {String|Array<string|Array<string>>} keys The name(s) of the key(s) to include.
  1492. * @param {Object} options
  1493. * Valid options are:<ul>
  1494. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  1495. * be used for this request.
  1496. * <li>sessionToken: A valid session token, used for making a request on
  1497. * behalf of a specific user.
  1498. * </ul>
  1499. * @static
  1500. */
  1501. static fetchAllWithInclude(list
  1502. /*: Array<ParseObject>*/
  1503. , keys
  1504. /*: String|Array<string|Array<string>>*/
  1505. , options
  1506. /*: RequestOptions*/
  1507. ) {
  1508. options = options || {};
  1509. options.include = keys;
  1510. return ParseObject.fetchAll(list, options);
  1511. }
  1512. /**
  1513. * Fetches the given list of Parse.Object if needed.
  1514. * If any error is encountered, stops and calls the error handler.
  1515. *
  1516. * <pre>
  1517. * Parse.Object.fetchAllIfNeeded([object1, ...])
  1518. * .then((list) => {
  1519. * // Objects were fetched and updated.
  1520. * }, (error) => {
  1521. * // An error occurred while fetching one of the objects.
  1522. * });
  1523. * </pre>
  1524. *
  1525. * @param {Array} list A list of <code>Parse.Object</code>.
  1526. * @param {Object} options
  1527. * @static
  1528. */
  1529. static fetchAllIfNeeded(list
  1530. /*: Array<ParseObject>*/
  1531. , options) {
  1532. options = options || {};
  1533. const queryOptions = {};
  1534. if (options.hasOwnProperty('useMasterKey')) {
  1535. queryOptions.useMasterKey = options.useMasterKey;
  1536. }
  1537. if (options.hasOwnProperty('sessionToken')) {
  1538. queryOptions.sessionToken = options.sessionToken;
  1539. }
  1540. return CoreManager.getObjectController().fetch(list, false, queryOptions);
  1541. }
  1542. /**
  1543. * Destroy the given list of models on the server if it was already persisted.
  1544. *
  1545. * <p>Unlike saveAll, if an error occurs while deleting an individual model,
  1546. * this method will continue trying to delete the rest of the models if
  1547. * possible, except in the case of a fatal error like a connection error.
  1548. *
  1549. * <p>In particular, the Parse.Error object returned in the case of error may
  1550. * be one of two types:
  1551. *
  1552. * <ul>
  1553. * <li>A Parse.Error.AGGREGATE_ERROR. This object's "errors" property is an
  1554. * array of other Parse.Error objects. Each error object in this array
  1555. * has an "object" property that references the object that could not be
  1556. * deleted (for instance, because that object could not be found).</li>
  1557. * <li>A non-aggregate Parse.Error. This indicates a serious error that
  1558. * caused the delete operation to be aborted partway through (for
  1559. * instance, a connection failure in the middle of the delete).</li>
  1560. * </ul>
  1561. *
  1562. * <pre>
  1563. * Parse.Object.destroyAll([object1, object2, ...])
  1564. * .then((list) => {
  1565. * // All the objects were deleted.
  1566. * }, (error) => {
  1567. * // An error occurred while deleting one or more of the objects.
  1568. * // If this is an aggregate error, then we can inspect each error
  1569. * // object individually to determine the reason why a particular
  1570. * // object was not deleted.
  1571. * if (error.code === Parse.Error.AGGREGATE_ERROR) {
  1572. * for (var i = 0; i < error.errors.length; i++) {
  1573. * console.log("Couldn't delete " + error.errors[i].object.id +
  1574. * "due to " + error.errors[i].message);
  1575. * }
  1576. * } else {
  1577. * console.log("Delete aborted because of " + error.message);
  1578. * }
  1579. * });
  1580. * </pre>
  1581. *
  1582. * @param {Array} list A list of <code>Parse.Object</code>.
  1583. * @param {Object} options
  1584. * @static
  1585. * Valid options are:<ul>
  1586. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  1587. * be used for this request.
  1588. * <li>sessionToken: A valid session token, used for making a request on
  1589. * behalf of a specific user.
  1590. * <li>batchSize: Number of objects to process per request
  1591. * </ul>
  1592. * @return {Promise} A promise that is fulfilled when the destroyAll
  1593. * completes.
  1594. */
  1595. static destroyAll(list
  1596. /*: Array<ParseObject>*/
  1597. , options = {}) {
  1598. const destroyOptions = {};
  1599. if (options.hasOwnProperty('useMasterKey')) {
  1600. destroyOptions.useMasterKey = options.useMasterKey;
  1601. }
  1602. if (options.hasOwnProperty('sessionToken')) {
  1603. destroyOptions.sessionToken = options.sessionToken;
  1604. }
  1605. if (options.hasOwnProperty('batchSize') && typeof options.batchSize === 'number') {
  1606. destroyOptions.batchSize = options.batchSize;
  1607. }
  1608. return CoreManager.getObjectController().destroy(list, destroyOptions);
  1609. }
  1610. /**
  1611. * Saves the given list of Parse.Object.
  1612. * If any error is encountered, stops and calls the error handler.
  1613. *
  1614. * <pre>
  1615. * Parse.Object.saveAll([object1, object2, ...])
  1616. * .then((list) => {
  1617. * // All the objects were saved.
  1618. * }, (error) => {
  1619. * // An error occurred while saving one of the objects.
  1620. * });
  1621. * </pre>
  1622. *
  1623. * @param {Array} list A list of <code>Parse.Object</code>.
  1624. * @param {Object} options
  1625. * @static
  1626. * Valid options are:<ul>
  1627. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  1628. * be used for this request.
  1629. * <li>sessionToken: A valid session token, used for making a request on
  1630. * behalf of a specific user.
  1631. * <li>batchSize: Number of objects to process per request
  1632. * </ul>
  1633. */
  1634. static saveAll(list
  1635. /*: Array<ParseObject>*/
  1636. , options
  1637. /*: RequestOptions*/
  1638. = {}) {
  1639. const saveOptions = {};
  1640. if (options.hasOwnProperty('useMasterKey')) {
  1641. saveOptions.useMasterKey = options.useMasterKey;
  1642. }
  1643. if (options.hasOwnProperty('sessionToken')) {
  1644. saveOptions.sessionToken = options.sessionToken;
  1645. }
  1646. if (options.hasOwnProperty('batchSize') && typeof options.batchSize === 'number') {
  1647. saveOptions.batchSize = options.batchSize;
  1648. }
  1649. return CoreManager.getObjectController().save(list, saveOptions);
  1650. }
  1651. /**
  1652. * Creates a reference to a subclass of Parse.Object with the given id. This
  1653. * does not exist on Parse.Object, only on subclasses.
  1654. *
  1655. * <p>A shortcut for: <pre>
  1656. * var Foo = Parse.Object.extend("Foo");
  1657. * var pointerToFoo = new Foo();
  1658. * pointerToFoo.id = "myObjectId";
  1659. * </pre>
  1660. *
  1661. * @param {String} id The ID of the object to create a reference to.
  1662. * @static
  1663. * @return {Parse.Object} A Parse.Object reference.
  1664. */
  1665. static createWithoutData(id
  1666. /*: string*/
  1667. ) {
  1668. const obj = new this();
  1669. obj.id = id;
  1670. return obj;
  1671. }
  1672. /**
  1673. * Creates a new instance of a Parse Object from a JSON representation.
  1674. * @param {Object} json The JSON map of the Object's data
  1675. * @param {boolean} override In single instance mode, all old server data
  1676. * is overwritten if this is set to true
  1677. * @static
  1678. * @return {Parse.Object} A Parse.Object reference
  1679. */
  1680. static fromJSON(json
  1681. /*: any*/
  1682. , override
  1683. /*:: ?: boolean*/
  1684. ) {
  1685. if (!json.className) {
  1686. throw new Error('Cannot create an object without a className');
  1687. }
  1688. const constructor = classMap[json.className];
  1689. const o = constructor ? new constructor() : new ParseObject(json.className);
  1690. const otherAttributes = {};
  1691. for (const attr in json) {
  1692. if (attr !== 'className' && attr !== '__type') {
  1693. otherAttributes[attr] = json[attr];
  1694. }
  1695. }
  1696. if (override) {
  1697. // id needs to be set before clearServerData can work
  1698. if (otherAttributes.objectId) {
  1699. o.id = otherAttributes.objectId;
  1700. }
  1701. let preserved = null;
  1702. if (typeof o._preserveFieldsOnFetch === 'function') {
  1703. preserved = o._preserveFieldsOnFetch();
  1704. }
  1705. o._clearServerData();
  1706. if (preserved) {
  1707. o._finishFetch(preserved);
  1708. }
  1709. }
  1710. o._finishFetch(otherAttributes);
  1711. if (json.objectId) {
  1712. o._setExisted(true);
  1713. }
  1714. return o;
  1715. }
  1716. /**
  1717. * Registers a subclass of Parse.Object with a specific class name.
  1718. * When objects of that class are retrieved from a query, they will be
  1719. * instantiated with this subclass.
  1720. * This is only necessary when using ES6 subclassing.
  1721. * @param {String} className The class name of the subclass
  1722. * @param {Class} constructor The subclass
  1723. */
  1724. static registerSubclass(className
  1725. /*: string*/
  1726. , constructor
  1727. /*: any*/
  1728. ) {
  1729. if (typeof className !== 'string') {
  1730. throw new TypeError('The first argument must be a valid class name.');
  1731. }
  1732. if (typeof constructor === 'undefined') {
  1733. throw new TypeError('You must supply a subclass constructor.');
  1734. }
  1735. if (typeof constructor !== 'function') {
  1736. throw new TypeError('You must register the subclass constructor. ' + 'Did you attempt to register an instance of the subclass?');
  1737. }
  1738. classMap[className] = constructor;
  1739. if (!constructor.className) {
  1740. constructor.className = className;
  1741. }
  1742. }
  1743. /**
  1744. * Creates a new subclass of Parse.Object for the given Parse class name.
  1745. *
  1746. * <p>Every extension of a Parse class will inherit from the most recent
  1747. * previous extension of that class. When a Parse.Object is automatically
  1748. * created by parsing JSON, it will use the most recent extension of that
  1749. * class.</p>
  1750. *
  1751. * <p>You should call either:<pre>
  1752. * var MyClass = Parse.Object.extend("MyClass", {
  1753. * <i>Instance methods</i>,
  1754. * initialize: function(attrs, options) {
  1755. * this.someInstanceProperty = [],
  1756. * <i>Other instance properties</i>
  1757. * }
  1758. * }, {
  1759. * <i>Class properties</i>
  1760. * });</pre>
  1761. * or, for Backbone compatibility:<pre>
  1762. * var MyClass = Parse.Object.extend({
  1763. * className: "MyClass",
  1764. * <i>Instance methods</i>,
  1765. * initialize: function(attrs, options) {
  1766. * this.someInstanceProperty = [],
  1767. * <i>Other instance properties</i>
  1768. * }
  1769. * }, {
  1770. * <i>Class properties</i>
  1771. * });</pre></p>
  1772. *
  1773. * @param {String} className The name of the Parse class backing this model.
  1774. * @param {Object} protoProps Instance properties to add to instances of the
  1775. * class returned from this method.
  1776. * @param {Object} classProps Class properties to add the class returned from
  1777. * this method.
  1778. * @return {Class} A new subclass of Parse.Object.
  1779. */
  1780. static extend(className
  1781. /*: any*/
  1782. , protoProps
  1783. /*: any*/
  1784. , classProps
  1785. /*: any*/
  1786. ) {
  1787. if (typeof className !== 'string') {
  1788. if (className && typeof className.className === 'string') {
  1789. return ParseObject.extend(className.className, className, protoProps);
  1790. } else {
  1791. throw new Error('Parse.Object.extend\'s first argument should be the className.');
  1792. }
  1793. }
  1794. let adjustedClassName = className;
  1795. if (adjustedClassName === 'User' && CoreManager.get('PERFORM_USER_REWRITE')) {
  1796. adjustedClassName = '_User';
  1797. }
  1798. let parentProto = ParseObject.prototype;
  1799. if (this.hasOwnProperty('__super__') && this.__super__) {
  1800. parentProto = this.prototype;
  1801. } else if (classMap[adjustedClassName]) {
  1802. parentProto = classMap[adjustedClassName].prototype;
  1803. }
  1804. const ParseObjectSubclass = function (attributes, options) {
  1805. this.className = adjustedClassName;
  1806. this._objCount = objectCount++; // Enable legacy initializers
  1807. if (typeof this.initialize === 'function') {
  1808. this.initialize.apply(this, arguments);
  1809. }
  1810. if (attributes && typeof attributes === 'object') {
  1811. if (!this.set(attributes || {}, options)) {
  1812. throw new Error('Can\'t create an invalid Parse Object');
  1813. }
  1814. }
  1815. };
  1816. ParseObjectSubclass.className = adjustedClassName;
  1817. ParseObjectSubclass.__super__ = parentProto;
  1818. ParseObjectSubclass.prototype = Object.create(parentProto, {
  1819. constructor: {
  1820. value: ParseObjectSubclass,
  1821. enumerable: false,
  1822. writable: true,
  1823. configurable: true
  1824. }
  1825. });
  1826. if (protoProps) {
  1827. for (const prop in protoProps) {
  1828. if (prop !== 'className') {
  1829. Object.defineProperty(ParseObjectSubclass.prototype, prop, {
  1830. value: protoProps[prop],
  1831. enumerable: false,
  1832. writable: true,
  1833. configurable: true
  1834. });
  1835. }
  1836. }
  1837. }
  1838. if (classProps) {
  1839. for (const prop in classProps) {
  1840. if (prop !== 'className') {
  1841. Object.defineProperty(ParseObjectSubclass, prop, {
  1842. value: classProps[prop],
  1843. enumerable: false,
  1844. writable: true,
  1845. configurable: true
  1846. });
  1847. }
  1848. }
  1849. }
  1850. ParseObjectSubclass.extend = function (name, protoProps, classProps) {
  1851. if (typeof name === 'string') {
  1852. return ParseObject.extend.call(ParseObjectSubclass, name, protoProps, classProps);
  1853. }
  1854. return ParseObject.extend.call(ParseObjectSubclass, adjustedClassName, name, protoProps);
  1855. };
  1856. ParseObjectSubclass.createWithoutData = ParseObject.createWithoutData;
  1857. classMap[adjustedClassName] = ParseObjectSubclass;
  1858. return ParseObjectSubclass;
  1859. }
  1860. /**
  1861. * Enable single instance objects, where any local objects with the same Id
  1862. * share the same attributes, and stay synchronized with each other.
  1863. * This is disabled by default in server environments, since it can lead to
  1864. * security issues.
  1865. * @static
  1866. */
  1867. static enableSingleInstance() {
  1868. singleInstance = true;
  1869. CoreManager.setObjectStateController(SingleInstanceStateController);
  1870. }
  1871. /**
  1872. * Disable single instance objects, where any local objects with the same Id
  1873. * share the same attributes, and stay synchronized with each other.
  1874. * When disabled, you can have two instances of the same object in memory
  1875. * without them sharing attributes.
  1876. * @static
  1877. */
  1878. static disableSingleInstance() {
  1879. singleInstance = false;
  1880. CoreManager.setObjectStateController(UniqueInstanceStateController);
  1881. }
  1882. /**
  1883. * Asynchronously stores the objects and every object they point to in the local datastore,
  1884. * recursively, using a default pin name: _default.
  1885. *
  1886. * If those other objects have not been fetched from Parse, they will not be stored.
  1887. * However, if they have changed data, all the changes will be retained.
  1888. *
  1889. * <pre>
  1890. * await Parse.Object.pinAll([...]);
  1891. * </pre>
  1892. *
  1893. * To retrieve object:
  1894. * <code>query.fromLocalDatastore()</code> or <code>query.fromPin()</code>
  1895. *
  1896. * @param {Array} objects A list of <code>Parse.Object</code>.
  1897. * @return {Promise} A promise that is fulfilled when the pin completes.
  1898. * @static
  1899. */
  1900. static pinAll(objects
  1901. /*: Array<ParseObject>*/
  1902. )
  1903. /*: Promise<void>*/
  1904. {
  1905. const localDatastore = CoreManager.getLocalDatastore();
  1906. if (!localDatastore.isEnabled) {
  1907. return Promise.reject('Parse.enableLocalDatastore() must be called first');
  1908. }
  1909. return ParseObject.pinAllWithName(DEFAULT_PIN, objects);
  1910. }
  1911. /**
  1912. * Asynchronously stores the objects and every object they point to in the local datastore, recursively.
  1913. *
  1914. * If those other objects have not been fetched from Parse, they will not be stored.
  1915. * However, if they have changed data, all the changes will be retained.
  1916. *
  1917. * <pre>
  1918. * await Parse.Object.pinAllWithName(name, [obj1, obj2, ...]);
  1919. * </pre>
  1920. *
  1921. * To retrieve object:
  1922. * <code>query.fromLocalDatastore()</code> or <code>query.fromPinWithName(name)</code>
  1923. *
  1924. * @param {String} name Name of Pin.
  1925. * @param {Array} objects A list of <code>Parse.Object</code>.
  1926. * @return {Promise} A promise that is fulfilled when the pin completes.
  1927. * @static
  1928. */
  1929. static pinAllWithName(name
  1930. /*: string*/
  1931. , objects
  1932. /*: Array<ParseObject>*/
  1933. )
  1934. /*: Promise<void>*/
  1935. {
  1936. const localDatastore = CoreManager.getLocalDatastore();
  1937. if (!localDatastore.isEnabled) {
  1938. return Promise.reject('Parse.enableLocalDatastore() must be called first');
  1939. }
  1940. return localDatastore._handlePinAllWithName(name, objects);
  1941. }
  1942. /**
  1943. * Asynchronously removes the objects and every object they point to in the local datastore,
  1944. * recursively, using a default pin name: _default.
  1945. *
  1946. * <pre>
  1947. * await Parse.Object.unPinAll([...]);
  1948. * </pre>
  1949. *
  1950. * @param {Array} objects A list of <code>Parse.Object</code>.
  1951. * @return {Promise} A promise that is fulfilled when the unPin completes.
  1952. * @static
  1953. */
  1954. static unPinAll(objects
  1955. /*: Array<ParseObject>*/
  1956. )
  1957. /*: Promise<void>*/
  1958. {
  1959. const localDatastore = CoreManager.getLocalDatastore();
  1960. if (!localDatastore.isEnabled) {
  1961. return Promise.reject('Parse.enableLocalDatastore() must be called first');
  1962. }
  1963. return ParseObject.unPinAllWithName(DEFAULT_PIN, objects);
  1964. }
  1965. /**
  1966. * Asynchronously removes the objects and every object they point to in the local datastore, recursively.
  1967. *
  1968. * <pre>
  1969. * await Parse.Object.unPinAllWithName(name, [obj1, obj2, ...]);
  1970. * </pre>
  1971. *
  1972. * @param {String} name Name of Pin.
  1973. * @param {Array} objects A list of <code>Parse.Object</code>.
  1974. * @return {Promise} A promise that is fulfilled when the unPin completes.
  1975. * @static
  1976. */
  1977. static unPinAllWithName(name
  1978. /*: string*/
  1979. , objects
  1980. /*: Array<ParseObject>*/
  1981. )
  1982. /*: Promise<void>*/
  1983. {
  1984. const localDatastore = CoreManager.getLocalDatastore();
  1985. if (!localDatastore.isEnabled) {
  1986. return Promise.reject('Parse.enableLocalDatastore() must be called first');
  1987. }
  1988. return localDatastore._handleUnPinAllWithName(name, objects);
  1989. }
  1990. /**
  1991. * Asynchronously removes all objects in the local datastore using a default pin name: _default.
  1992. *
  1993. * <pre>
  1994. * await Parse.Object.unPinAllObjects();
  1995. * </pre>
  1996. *
  1997. * @return {Promise} A promise that is fulfilled when the unPin completes.
  1998. * @static
  1999. */
  2000. static unPinAllObjects()
  2001. /*: Promise<void>*/
  2002. {
  2003. const localDatastore = CoreManager.getLocalDatastore();
  2004. if (!localDatastore.isEnabled) {
  2005. return Promise.reject('Parse.enableLocalDatastore() must be called first');
  2006. }
  2007. return localDatastore.unPinWithName(DEFAULT_PIN);
  2008. }
  2009. /**
  2010. * Asynchronously removes all objects with the specified pin name.
  2011. * Deletes the pin name also.
  2012. *
  2013. * <pre>
  2014. * await Parse.Object.unPinAllObjectsWithName(name);
  2015. * </pre>
  2016. *
  2017. * @param {String} name Name of Pin.
  2018. * @return {Promise} A promise that is fulfilled when the unPin completes.
  2019. * @static
  2020. */
  2021. static unPinAllObjectsWithName(name
  2022. /*: string*/
  2023. )
  2024. /*: Promise<void>*/
  2025. {
  2026. const localDatastore = CoreManager.getLocalDatastore();
  2027. if (!localDatastore.isEnabled) {
  2028. return Promise.reject('Parse.enableLocalDatastore() must be called first');
  2029. }
  2030. return localDatastore.unPinWithName(PIN_PREFIX + name);
  2031. }
  2032. }
  2033. const DefaultController = {
  2034. fetch(target
  2035. /*: ParseObject | Array<ParseObject>*/
  2036. , forceFetch
  2037. /*: boolean*/
  2038. , options
  2039. /*: RequestOptions*/
  2040. )
  2041. /*: Promise<Array<void> | ParseObject>*/
  2042. {
  2043. const localDatastore = CoreManager.getLocalDatastore();
  2044. if (Array.isArray(target)) {
  2045. if (target.length < 1) {
  2046. return Promise.resolve([]);
  2047. }
  2048. const objs = [];
  2049. const ids = [];
  2050. let className = null;
  2051. const results = [];
  2052. let error = null;
  2053. target.forEach(el => {
  2054. if (error) {
  2055. return;
  2056. }
  2057. if (!className) {
  2058. className = el.className;
  2059. }
  2060. if (className !== el.className) {
  2061. error = new ParseError(ParseError.INVALID_CLASS_NAME, 'All objects should be of the same class');
  2062. }
  2063. if (!el.id) {
  2064. error = new ParseError(ParseError.MISSING_OBJECT_ID, 'All objects must have an ID');
  2065. }
  2066. if (forceFetch || !el.isDataAvailable()) {
  2067. ids.push(el.id);
  2068. objs.push(el);
  2069. }
  2070. results.push(el);
  2071. });
  2072. if (error) {
  2073. return Promise.reject(error);
  2074. }
  2075. const query = new ParseQuery(className);
  2076. query.containedIn('objectId', ids);
  2077. if (options && options.include) {
  2078. query.include(options.include);
  2079. }
  2080. query._limit = ids.length;
  2081. return query.find(options).then(async objects => {
  2082. const idMap = {};
  2083. objects.forEach(o => {
  2084. idMap[o.id] = o;
  2085. });
  2086. for (let i = 0; i < objs.length; i++) {
  2087. const obj = objs[i];
  2088. if (!obj || !obj.id || !idMap[obj.id]) {
  2089. if (forceFetch) {
  2090. return Promise.reject(new ParseError(ParseError.OBJECT_NOT_FOUND, 'All objects must exist on the server.'));
  2091. }
  2092. }
  2093. }
  2094. if (!singleInstance) {
  2095. // If single instance objects are disabled, we need to replace the
  2096. for (let i = 0; i < results.length; i++) {
  2097. const obj = results[i];
  2098. if (obj && obj.id && idMap[obj.id]) {
  2099. const id = obj.id;
  2100. obj._finishFetch(idMap[id].toJSON());
  2101. results[i] = idMap[id];
  2102. }
  2103. }
  2104. }
  2105. for (const object of results) {
  2106. await localDatastore._updateObjectIfPinned(object);
  2107. }
  2108. return Promise.resolve(results);
  2109. });
  2110. } else {
  2111. const RESTController = CoreManager.getRESTController();
  2112. const params = {};
  2113. if (options && options.include) {
  2114. params.include = options.include.join();
  2115. }
  2116. return RESTController.request('GET', 'classes/' + target.className + '/' + target._getId(), params, options).then(async response => {
  2117. if (target instanceof ParseObject) {
  2118. target._clearPendingOps();
  2119. target._clearServerData();
  2120. target._finishFetch(response);
  2121. }
  2122. await localDatastore._updateObjectIfPinned(target);
  2123. return target;
  2124. });
  2125. }
  2126. },
  2127. async destroy(target
  2128. /*: ParseObject | Array<ParseObject>*/
  2129. , options
  2130. /*: RequestOptions*/
  2131. )
  2132. /*: Promise<Array<void> | ParseObject>*/
  2133. {
  2134. const batchSize = options && options.batchSize ? options.batchSize : DEFAULT_BATCH_SIZE;
  2135. const localDatastore = CoreManager.getLocalDatastore();
  2136. const RESTController = CoreManager.getRESTController();
  2137. if (Array.isArray(target)) {
  2138. if (target.length < 1) {
  2139. return Promise.resolve([]);
  2140. }
  2141. const batches = [[]];
  2142. target.forEach(obj => {
  2143. if (!obj.id) {
  2144. return;
  2145. }
  2146. batches[batches.length - 1].push(obj);
  2147. if (batches[batches.length - 1].length >= batchSize) {
  2148. batches.push([]);
  2149. }
  2150. });
  2151. if (batches[batches.length - 1].length === 0) {
  2152. // If the last batch is empty, remove it
  2153. batches.pop();
  2154. }
  2155. let deleteCompleted = Promise.resolve();
  2156. const errors = [];
  2157. batches.forEach(batch => {
  2158. deleteCompleted = deleteCompleted.then(() => {
  2159. return RESTController.request('POST', 'batch', {
  2160. requests: batch.map(obj => {
  2161. return {
  2162. method: 'DELETE',
  2163. path: getServerUrlPath() + 'classes/' + obj.className + '/' + obj._getId(),
  2164. body: {}
  2165. };
  2166. })
  2167. }, options).then(results => {
  2168. for (let i = 0; i < results.length; i++) {
  2169. if (results[i] && results[i].hasOwnProperty('error')) {
  2170. const err = new ParseError(results[i].error.code, results[i].error.error);
  2171. err.object = batch[i];
  2172. errors.push(err);
  2173. }
  2174. }
  2175. });
  2176. });
  2177. });
  2178. return deleteCompleted.then(async () => {
  2179. if (errors.length) {
  2180. const aggregate = new ParseError(ParseError.AGGREGATE_ERROR);
  2181. aggregate.errors = errors;
  2182. return Promise.reject(aggregate);
  2183. }
  2184. for (const object of target) {
  2185. await localDatastore._destroyObjectIfPinned(object);
  2186. }
  2187. return Promise.resolve(target);
  2188. });
  2189. } else if (target instanceof ParseObject) {
  2190. return RESTController.request('DELETE', 'classes/' + target.className + '/' + target._getId(), {}, options).then(async () => {
  2191. await localDatastore._destroyObjectIfPinned(target);
  2192. return Promise.resolve(target);
  2193. });
  2194. }
  2195. await localDatastore._destroyObjectIfPinned(target);
  2196. return Promise.resolve(target);
  2197. },
  2198. save(target
  2199. /*: ParseObject | Array<ParseObject | ParseFile>*/
  2200. , options
  2201. /*: RequestOptions*/
  2202. ) {
  2203. const batchSize = options && options.batchSize ? options.batchSize : DEFAULT_BATCH_SIZE;
  2204. const localDatastore = CoreManager.getLocalDatastore();
  2205. const mapIdForPin = {};
  2206. const RESTController = CoreManager.getRESTController();
  2207. const stateController = CoreManager.getObjectStateController();
  2208. options = options || {};
  2209. options.returnStatus = options.returnStatus || true;
  2210. if (Array.isArray(target)) {
  2211. if (target.length < 1) {
  2212. return Promise.resolve([]);
  2213. }
  2214. let unsaved = target.concat();
  2215. for (let i = 0; i < target.length; i++) {
  2216. if (target[i] instanceof ParseObject) {
  2217. unsaved = unsaved.concat(unsavedChildren(target[i], true));
  2218. }
  2219. }
  2220. unsaved = unique(unsaved);
  2221. let filesSaved = Promise.resolve();
  2222. let pending
  2223. /*: Array<ParseObject>*/
  2224. = [];
  2225. unsaved.forEach(el => {
  2226. if (el instanceof ParseFile) {
  2227. filesSaved = filesSaved.then(() => {
  2228. return el.save();
  2229. });
  2230. } else if (el instanceof ParseObject) {
  2231. pending.push(el);
  2232. }
  2233. });
  2234. return filesSaved.then(() => {
  2235. let objectError = null;
  2236. return continueWhile(() => {
  2237. return pending.length > 0;
  2238. }, () => {
  2239. const batch = [];
  2240. const nextPending = [];
  2241. pending.forEach(el => {
  2242. if (batch.length < batchSize && canBeSerialized(el)) {
  2243. batch.push(el);
  2244. } else {
  2245. nextPending.push(el);
  2246. }
  2247. });
  2248. pending = nextPending;
  2249. if (batch.length < 1) {
  2250. return Promise.reject(new ParseError(ParseError.OTHER_CAUSE, 'Tried to save a batch with a cycle.'));
  2251. } // Queue up tasks for each object in the batch.
  2252. // When every task is ready, the API request will execute
  2253. let res, rej;
  2254. const batchReturned = new Promise((resolve, reject) => {
  2255. res = resolve;
  2256. rej = reject;
  2257. });
  2258. batchReturned.resolve = res;
  2259. batchReturned.reject = rej;
  2260. const batchReady = [];
  2261. const batchTasks = [];
  2262. batch.forEach((obj, index) => {
  2263. let res, rej;
  2264. const ready = new Promise((resolve, reject) => {
  2265. res = resolve;
  2266. rej = reject;
  2267. });
  2268. ready.resolve = res;
  2269. ready.reject = rej;
  2270. batchReady.push(ready);
  2271. stateController.pushPendingState(obj._getStateIdentifier());
  2272. batchTasks.push(stateController.enqueueTask(obj._getStateIdentifier(), function () {
  2273. ready.resolve();
  2274. return batchReturned.then(responses => {
  2275. if (responses[index].hasOwnProperty('success')) {
  2276. const objectId = responses[index].success.objectId;
  2277. const status = responses[index]._status;
  2278. delete responses[index]._status;
  2279. mapIdForPin[objectId] = obj._localId;
  2280. obj._handleSaveResponse(responses[index].success, status);
  2281. } else {
  2282. if (!objectError && responses[index].hasOwnProperty('error')) {
  2283. const serverError = responses[index].error;
  2284. objectError = new ParseError(serverError.code, serverError.error); // Cancel the rest of the save
  2285. pending = [];
  2286. }
  2287. obj._handleSaveError();
  2288. }
  2289. });
  2290. }));
  2291. });
  2292. when(batchReady).then(() => {
  2293. // Kick off the batch request
  2294. return RESTController.request('POST', 'batch', {
  2295. requests: batch.map(obj => {
  2296. const params = obj._getSaveParams();
  2297. params.path = getServerUrlPath() + params.path;
  2298. return params;
  2299. })
  2300. }, options);
  2301. }).then(batchReturned.resolve, error => {
  2302. batchReturned.reject(new ParseError(ParseError.INCORRECT_TYPE, error.message));
  2303. });
  2304. return when(batchTasks);
  2305. }).then(async () => {
  2306. if (objectError) {
  2307. return Promise.reject(objectError);
  2308. }
  2309. for (const object of target) {
  2310. await localDatastore._updateLocalIdForObject(mapIdForPin[object.id], object);
  2311. await localDatastore._updateObjectIfPinned(object);
  2312. }
  2313. return Promise.resolve(target);
  2314. });
  2315. });
  2316. } else if (target instanceof ParseObject) {
  2317. // copying target lets Flow guarantee the pointer isn't modified elsewhere
  2318. const localId = target._localId;
  2319. const targetCopy = target;
  2320. const task = function () {
  2321. const params = targetCopy._getSaveParams();
  2322. return RESTController.request(params.method, params.path, params.body, options).then(response => {
  2323. const status = response._status;
  2324. delete response._status;
  2325. targetCopy._handleSaveResponse(response, status);
  2326. }, error => {
  2327. targetCopy._handleSaveError();
  2328. return Promise.reject(error);
  2329. });
  2330. };
  2331. stateController.pushPendingState(target._getStateIdentifier());
  2332. return stateController.enqueueTask(target._getStateIdentifier(), task).then(async () => {
  2333. await localDatastore._updateLocalIdForObject(localId, target);
  2334. await localDatastore._updateObjectIfPinned(target);
  2335. return target;
  2336. }, error => {
  2337. return Promise.reject(error);
  2338. });
  2339. }
  2340. return Promise.resolve();
  2341. }
  2342. };
  2343. CoreManager.setObjectController(DefaultController);
  2344. export default ParseObject;