UNPKG

@apollographql/apollo-upload-server

Version:

Enhances Apollo GraphQL Server for intuitive file uploads via GraphQL mutations.

133 lines (110 loc) 4.63 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.apolloUploadExpress = exports.apolloUploadKoa = exports.processRequest = void 0; var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/asyncToGenerator")); var _map = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/map")); var _entries = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/entries")); var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise")); var _busboy = _interopRequireDefault(require("busboy")); var _objectPath = _interopRequireDefault(require("object-path")); var _errors = require("./errors"); class Upload { constructor() { this.promise = new _promise.default((resolve, reject) => { this.reject = reject; this.resolve = file => { this.file = file; file.stream.once('end', () => { this.done = true; }); file.stream.once('limit', () => file.stream.emit('error', new _errors.MaxFileSizeUploadError('File truncated as it exceeds the size limit.'))); resolve(file); }; }); } } const processRequest = (request, { maxFieldSize, maxFileSize, maxFiles } = {}) => new _promise.default((resolve, reject) => { const parser = new _busboy.default({ headers: request.headers, limits: { fieldSize: maxFieldSize, fields: 2, fileSize: maxFileSize, files: maxFiles } }); let operations; let operationsPath; let map; parser.on('field', (fieldName, value) => { switch (fieldName) { case 'operations': operations = JSON.parse(value); operationsPath = (0, _objectPath.default)(operations); break; case 'map': { if (!operations) return reject(new _errors.MapBeforeOperationsUploadError(`Misordered multipart fields; “map” should follow “operations” (${_errors.SPEC_URL}).`, 400)); const mapEntries = (0, _entries.default)(JSON.parse(value)); if (mapEntries.length > maxFiles) return reject(new _errors.MaxFilesUploadError(`${maxFiles} max file uploads exceeded.`, 413)); map = new _map.default(); for (const [fieldName, paths] of mapEntries) { map.set(fieldName, new Upload()); for (const path of paths) operationsPath.set(path, map.get(fieldName).promise); } resolve(operations); } } }); parser.on('file', (fieldName, stream, filename, encoding, mimetype) => { if (!map) return reject(new _errors.FilesBeforeMapUploadError(`Misordered multipart fields; files should follow “map” (${_errors.SPEC_URL}).`, 400)); if (map.has(fieldName)) map.get(fieldName).resolve({ stream, filename, mimetype, encoding });else stream.resume(); }); parser.once('filesLimit', () => { if (map) for (const upload of map.values()) if (!upload.file) upload.reject(new _errors.MaxFilesUploadError(`${maxFiles} max file uploads exceeded.`)); }); parser.once('finish', () => { if (map) for (const upload of map.values()) if (!upload.file) upload.reject(new _errors.FileMissingUploadError('File missing in the request.')); }); request.on('close', () => { if (map) for (const upload of map.values()) if (!upload.file) upload.reject(new _errors.UploadPromiseDisconnectUploadError('Request disconnected before file upload stream parsing.'));else if (!upload.done) { upload.file.stream.truncated = true; upload.file.stream.emit('error', new _errors.FileStreamDisconnectUploadError('Request disconnected during file upload stream parsing.')); } }); request.pipe(parser); }); exports.processRequest = processRequest; const apolloUploadKoa = options => function () { var _ref = (0, _asyncToGenerator2.default)(function* (ctx, next) { if (ctx.request.is('multipart/form-data')) ctx.request.body = yield processRequest(ctx.req, options); yield next(); }); return function (_x, _x2) { return _ref.apply(this, arguments); }; }(); exports.apolloUploadKoa = apolloUploadKoa; const apolloUploadExpress = options => (request, response, next) => { if (!request.is('multipart/form-data')) return next(); processRequest(request, options).then(body => { request.body = body; next(); }).catch(error => { if (error.status && error.expose) response.status(error.status); next(error); }); }; exports.apolloUploadExpress = apolloUploadExpress;