Skip to content

Commit 2b55bdf

Browse files
authored
feat: Add transaction to save and destroy on Parse.Object (#2265)
1 parent 5152401 commit 2b55bdf

File tree

3 files changed

+336
-34
lines changed

3 files changed

+336
-34
lines changed

src/ParseObject.ts

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type SaveOptions = FullOptions & {
4747
cascadeSave?: boolean;
4848
context?: AttributeMap;
4949
batchSize?: number;
50+
transaction?: boolean;
5051
};
5152

5253
type FetchOptions = {
@@ -1354,6 +1355,29 @@ class ParseObject {
13541355
}
13551356
const controller = CoreManager.getObjectController();
13561357
const unsaved = options.cascadeSave !== false ? unsavedChildren(this) : null;
1358+
if (
1359+
unsaved &&
1360+
unsaved.length &&
1361+
options.transaction === true &&
1362+
unsaved.some(el => el instanceof ParseObject)
1363+
) {
1364+
saveOptions.transaction = options.transaction;
1365+
const unsavedFiles: ParseFile[] = [];
1366+
const unsavedObjects: ParseObject[] = [];
1367+
unsaved.forEach(el => {
1368+
if (el instanceof ParseFile) unsavedFiles.push(el);
1369+
else unsavedObjects.push(el);
1370+
});
1371+
unsavedObjects.push(this);
1372+
1373+
const filePromise = unsavedFiles.length
1374+
? controller.save(unsavedFiles, saveOptions)
1375+
: Promise.resolve();
1376+
1377+
return filePromise
1378+
.then(() => controller.save(unsavedObjects, saveOptions))
1379+
.then((savedOjbects: this[]) => savedOjbects.pop());
1380+
}
13571381
return controller.save(unsaved, saveOptions).then(() => {
13581382
return controller.save(this, saveOptions);
13591383
}) as Promise<ParseObject> as Promise<this>;
@@ -1770,6 +1794,9 @@ class ParseObject {
17701794
if (options.hasOwnProperty('sessionToken')) {
17711795
destroyOptions.sessionToken = options.sessionToken;
17721796
}
1797+
if (options.hasOwnProperty('transaction') && typeof options.transaction === 'boolean') {
1798+
destroyOptions.transaction = options.transaction;
1799+
}
17731800
if (options.hasOwnProperty('batchSize') && typeof options.batchSize === 'number') {
17741801
destroyOptions.batchSize = options.batchSize;
17751802
}
@@ -1805,6 +1832,9 @@ class ParseObject {
18051832
if (options.hasOwnProperty('sessionToken')) {
18061833
saveOptions.sessionToken = options.sessionToken;
18071834
}
1835+
if (options.hasOwnProperty('transaction') && typeof options.transaction === 'boolean') {
1836+
saveOptions.transaction = options.transaction;
1837+
}
18081838
if (options.hasOwnProperty('batchSize') && typeof options.batchSize === 'number') {
18091839
saveOptions.batchSize = options.batchSize;
18101840
}
@@ -2322,12 +2352,20 @@ const DefaultController = {
23222352
target: ParseObject | Array<ParseObject>,
23232353
options: RequestOptions
23242354
): Promise<ParseObject | Array<ParseObject>> {
2325-
const batchSize =
2355+
if (options && options.batchSize && options.transaction)
2356+
throw new ParseError(
2357+
ParseError.OTHER_CAUSE,
2358+
'You cannot use both transaction and batchSize options simultaneously.'
2359+
);
2360+
2361+
let batchSize =
23262362
options && options.batchSize ? options.batchSize : CoreManager.get('REQUEST_BATCH_SIZE');
23272363
const localDatastore = CoreManager.getLocalDatastore();
23282364

23292365
const RESTController = CoreManager.getRESTController();
23302366
if (Array.isArray(target)) {
2367+
if (options && options.transaction && target.length > 1) batchSize = target.length;
2368+
23312369
if (target.length < 1) {
23322370
return Promise.resolve([]);
23332371
}
@@ -2348,21 +2386,20 @@ const DefaultController = {
23482386
let deleteCompleted = Promise.resolve();
23492387
const errors = [];
23502388
batches.forEach(batch => {
2389+
const requests = batch.map(obj => {
2390+
return {
2391+
method: 'DELETE',
2392+
path: getServerUrlPath() + 'classes/' + obj.className + '/' + obj._getId(),
2393+
body: {},
2394+
};
2395+
});
2396+
const body =
2397+
options && options.transaction && requests.length > 1
2398+
? { requests, transaction: true }
2399+
: { requests };
2400+
23512401
deleteCompleted = deleteCompleted.then(() => {
2352-
return RESTController.request(
2353-
'POST',
2354-
'batch',
2355-
{
2356-
requests: batch.map(obj => {
2357-
return {
2358-
method: 'DELETE',
2359-
path: getServerUrlPath() + 'classes/' + obj.className + '/' + obj._getId(),
2360-
body: {},
2361-
};
2362-
}),
2363-
},
2364-
options
2365-
).then(results => {
2402+
return RESTController.request('POST', 'batch', body, options).then(results => {
23662403
for (let i = 0; i < results.length; i++) {
23672404
if (results[i] && results[i].hasOwnProperty('error')) {
23682405
const err = new ParseError(results[i].error.code, results[i].error.error);
@@ -2402,8 +2439,17 @@ const DefaultController = {
24022439
target: ParseObject | null | Array<ParseObject | ParseFile>,
24032440
options: RequestOptions
24042441
): Promise<ParseObject | Array<ParseObject> | ParseFile | undefined> {
2405-
const batchSize =
2442+
if (options && options.batchSize && options.transaction)
2443+
return Promise.reject(
2444+
new ParseError(
2445+
ParseError.OTHER_CAUSE,
2446+
'You cannot use both transaction and batchSize options simultaneously.'
2447+
)
2448+
);
2449+
2450+
let batchSize =
24062451
options && options.batchSize ? options.batchSize : CoreManager.get('REQUEST_BATCH_SIZE');
2452+
24072453
const localDatastore = CoreManager.getLocalDatastore();
24082454
const mapIdForPin = {};
24092455

@@ -2437,6 +2483,17 @@ const DefaultController = {
24372483
}
24382484
});
24392485

2486+
if (options && options.transaction && pending.length > 1) {
2487+
if (pending.some(el => !canBeSerialized(el)))
2488+
return Promise.reject(
2489+
new ParseError(
2490+
ParseError.OTHER_CAUSE,
2491+
'Tried to save a transactional batch containing an object with unserializable attributes.'
2492+
)
2493+
);
2494+
batchSize = pending.length;
2495+
}
2496+
24402497
return Promise.all(filesSaved).then(() => {
24412498
let objectError = null;
24422499
return continueWhile(
@@ -2504,18 +2561,16 @@ const DefaultController = {
25042561
when(batchReady)
25052562
.then(() => {
25062563
// Kick off the batch request
2507-
return RESTController.request(
2508-
'POST',
2509-
'batch',
2510-
{
2511-
requests: batch.map(obj => {
2512-
const params = obj._getSaveParams();
2513-
params.path = getServerUrlPath() + params.path;
2514-
return params;
2515-
}),
2516-
},
2517-
options
2518-
);
2564+
const requests = batch.map(obj => {
2565+
const params = obj._getSaveParams();
2566+
params.path = getServerUrlPath() + params.path;
2567+
return params;
2568+
});
2569+
const body =
2570+
options && options.transaction && requests.length > 1
2571+
? { requests, transaction: true }
2572+
: { requests };
2573+
return RESTController.request('POST', 'batch', body, options);
25192574
})
25202575
.then(batchReturned.resolve, error => {
25212576
batchReturned.reject(new ParseError(ParseError.INCORRECT_TYPE, error.message));

src/RESTController.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type RequestOptions = {
1616
context?: any;
1717
usePost?: boolean;
1818
ignoreEmailVerification?: boolean;
19+
transaction?: boolean;
1920
};
2021

2122
export type FullOptions = {

0 commit comments

Comments
 (0)