Files
kami-parse-server/src/batch.js
Colin Ulin e0480dfa8d feat: Upgrade to express 5.0.1 (#9530)
BREAKING CHANGE: This upgrades the internally used Express framework from version 4 to 5, which may be a breaking change. If Parse Server is set up to be mounted on an Express application, we recommend to also use version 5 of the Express framework to avoid any compatibility issues. Note that even if there are no issues after upgrading, future releases of Parse Server may introduce issues if Parse Server internally relies on Express 5-specific features which are unsupported by the Express version on which it is mounted. See the Express [migration guide](https://expressjs.com/en/guide/migrating-5.html) and [release announcement](https://expressjs.com/2024/10/15/v5-release.html#breaking-changes) for more info.
2025-03-03 22:11:42 +01:00

152 lines
4.8 KiB
JavaScript

const Parse = require('parse/node').Parse;
const path = require('path');
// These methods handle batch requests.
const batchPath = '/batch';
// Mounts a batch-handler onto a PromiseRouter.
function mountOnto(router) {
router.route('POST', batchPath, req => {
return handleBatch(router, req);
});
}
function parseURL(urlString) {
try {
return new URL(urlString);
} catch (error) {
return undefined;
}
}
function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) {
serverURL = serverURL ? parseURL(serverURL) : undefined;
publicServerURL = publicServerURL ? parseURL(publicServerURL) : undefined;
const apiPrefixLength = originalUrl.length - batchPath.length;
let apiPrefix = originalUrl.slice(0, apiPrefixLength);
const makeRoutablePath = function (requestPath) {
// The routablePath is the path minus the api prefix
if (requestPath.slice(0, apiPrefix.length) != apiPrefix) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot route batch path ' + requestPath);
}
return path.posix.join('/', requestPath.slice(apiPrefix.length));
};
if (serverURL && publicServerURL && serverURL.pathname != publicServerURL.pathname) {
const localPath = serverURL.pathname;
const publicPath = publicServerURL.pathname;
// Override the api prefix
apiPrefix = localPath;
return function (requestPath) {
// Figure out which server url was used by figuring out which
// path more closely matches requestPath
const startsWithLocal = requestPath.startsWith(localPath);
const startsWithPublic = requestPath.startsWith(publicPath);
const pathLengthToUse =
startsWithLocal && startsWithPublic
? Math.max(localPath.length, publicPath.length)
: startsWithLocal
? localPath.length
: publicPath.length;
const newPath = path.posix.join('/', localPath, '/', requestPath.slice(pathLengthToUse));
// Use the method for local routing
return makeRoutablePath(newPath);
};
}
return makeRoutablePath;
}
// Returns a promise for a {response} object.
// TODO: pass along auth correctly
function handleBatch(router, req) {
if (!Array.isArray(req.body?.requests)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'requests must be an array');
}
// The batch paths are all from the root of our domain.
// That means they include the API prefix, that the API is mounted
// to. However, our promise router does not route the api prefix. So
// we need to figure out the API prefix, so that we can strip it
// from all the subrequests.
if (!req.originalUrl.endsWith(batchPath)) {
throw 'internal routing problem - expected url to end with batch';
}
const makeRoutablePath = makeBatchRoutingPathFunction(
req.originalUrl,
req.config.serverURL,
req.config.publicServerURL
);
const batch = transactionRetries => {
let initialPromise = Promise.resolve();
if (req.body?.transaction === true) {
initialPromise = req.config.database.createTransactionalSession();
}
return initialPromise.then(() => {
const promises = req.body?.requests.map(restRequest => {
const routablePath = makeRoutablePath(restRequest.path);
// Construct a request that we can send to a handler
const request = {
body: restRequest.body,
config: req.config,
auth: req.auth,
info: req.info,
};
return router.tryRouteRequest(restRequest.method, routablePath, request).then(
response => {
return { success: response.response };
},
error => {
return { error: { code: error.code, error: error.message } };
}
);
});
return Promise.all(promises)
.then(results => {
if (req.body?.transaction === true) {
if (results.find(result => typeof result.error === 'object')) {
return req.config.database.abortTransactionalSession().then(() => {
return Promise.reject({ response: results });
});
} else {
return req.config.database.commitTransactionalSession().then(() => {
return { response: results };
});
}
} else {
return { response: results };
}
})
.catch(error => {
if (
error &&
error.response &&
error.response.find(
errorItem => typeof errorItem.error === 'object' && errorItem.error.code === 251
) &&
transactionRetries > 0
) {
return batch(transactionRetries - 1);
}
throw error;
});
});
};
return batch(5);
}
module.exports = {
mountOnto,
makeBatchRoutingPathFunction,
};