diff --git a/package-lock.json b/package-lock.json index a2cc885..2e083bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@themost/express", - "version": "2.5.2", + "version": "2.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@themost/express", - "version": "2.5.2", + "version": "2.6.0", "license": "BSD-3-Clause", "dependencies": { "express": "^4.21.2", @@ -15,7 +15,7 @@ "on-headers": "^1.0.2", "pluralize": "^7.0.0", "q": "^1.5.1", - "rxjs": "^6.5.4", + "rxjs": "^7.8.2", "symbol": "^0.3.1" }, "devDependencies": { @@ -29,6 +29,7 @@ "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.24.0", "@rollup/plugin-babel": "^5.3.1", + "@themost/client": "^2.16.3", "@themost/common": "^2.12.0", "@themost/data": "^2.21.3", "@themost/events": "^1.5.0", @@ -61,7 +62,6 @@ "rollup-plugin-auto-external": "^2.0.0", "rollup-plugin-dts": "^4.2.3", "rollup-plugin-node-resolve": "^5.2.0", - "rxjs": "^6.5.4", "supertest": "^4.0.2", "symbol": "^0.3.1", "typescript": "^4.0.3" @@ -2303,6 +2303,19 @@ "node": ">=4.0" } }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2394,6 +2407,16 @@ "node": ">=10" } }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -2464,6 +2487,120 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@themost/client": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@themost/client/-/client-2.16.3.tgz", + "integrity": "sha512-LZjJVN6UjsSmLEG/wnwpUakwjVQIKuvwxVFUhQ6eduMdTao85dkwRE+smTiKdRkJE0f+bONxLCIYjEVAtvoWgA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@themost/events": "^1.1.0", + "@themost/json": "^1.0.1", + "@themost/query": "^2.13.1", + "@themost/xml": "^2.5.1", + "buffer": "^6.0.3", + "minimist": "^1.2.8", + "superagent": "^9.0.1" + }, + "bin": { + "client-cli": "util/bin/cli.js" + } + }, + "node_modules/@themost/client/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@themost/client/node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@themost/client/node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/@themost/client/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@themost/client/node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "deprecated": "Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/@themost/common": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/@themost/common/-/common-2.12.0.tgz", @@ -3494,6 +3631,13 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, "node_modules/async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -4509,6 +4653,17 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -5244,6 +5399,13 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -7355,10 +7517,14 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minipass": { "version": "3.3.6", @@ -8651,15 +8817,12 @@ } }, "node_modules/rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, "node_modules/safe-buffer": { @@ -9476,10 +9639,10 @@ } }, "node_modules/tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/tunnel-agent": { "version": "0.6.0", diff --git a/package.json b/package.json index 52b7926..f463a1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@themost/express", - "version": "2.5.2", + "version": "2.6.0", "description": "@themost-framework express.js middleware", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", @@ -48,7 +48,7 @@ "on-headers": "^1.0.2", "pluralize": "^7.0.0", "q": "^1.5.1", - "rxjs": "^6.5.4", + "rxjs": "^7.8.2", "symbol": "^0.3.1" }, "devDependencies": { @@ -62,6 +62,7 @@ "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.24.0", "@rollup/plugin-babel": "^5.3.1", + "@themost/client": "^2.16.3", "@themost/common": "^2.12.0", "@themost/data": "^2.21.3", "@themost/events": "^1.5.0", @@ -94,7 +95,6 @@ "rollup-plugin-auto-external": "^2.0.0", "rollup-plugin-dts": "^4.2.3", "rollup-plugin-node-resolve": "^5.2.0", - "rxjs": "^6.5.4", "supertest": "^4.0.2", "symbol": "^0.3.1", "typescript": "^4.0.3" diff --git a/spec/batch.spec.js b/spec/batch.spec.js new file mode 100644 index 0000000..c960f2e --- /dev/null +++ b/spec/batch.spec.js @@ -0,0 +1,68 @@ +import express from 'express'; +import {ExpressDataApplication} from '@themost/express'; +import path from 'path'; +import {dateReviver} from '@themost/express'; +import passport from 'passport'; +import {serviceRouter} from '@themost/express'; +import {TestPassportStrategy} from './passport'; +import request from 'supertest'; +import { finalizeDataApplication } from './utils'; + +describe('Batch Processing', () => { + let app; + let passportStrategy = new TestPassportStrategy(); + beforeAll(() => { + app = express(); + // create a new instance of data application + const dataApplication = new ExpressDataApplication(path.resolve(__dirname, 'test/config')); + app.use(express.json({ + reviver: dateReviver + })); + // hold data application + app.set('ExpressDataApplication', dataApplication); + // use data middleware (register req.context) + app.use(dataApplication.middleware(app)); + // use test passport strategy + // noinspection JSCheckFunctionSignatures + passport.use(passportStrategy); + // set testRouter + // noinspection JSCheckFunctionSignatures + app.use('/api/', passport.authenticate('bearer', { session: false }), serviceRouter); + }); + + afterAll(async () => { + const dataApplication = app.get('ExpressDataApplication'); + await finalizeDataApplication(dataApplication); + }); + + it('should post batch request', async () => { + const mock = jest.spyOn(passportStrategy, 'getUser'); + mock.mockImplementation(() => { + return { + name: 'alexis.rees@example.com' + }; + }); + let response = await request(app) + .post('/api/$batch') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .send({ + requests: [ + { + id: '1', + method: 'GET', + url: '/users/me' + }, + { + id: '2', + method: 'GET', + url: '/users/active?name=?' + } + ] + }); + expect(response.status).toEqual(200); + expect(response.body).toBeTruthy(); + }); + + +}); diff --git a/spec/test/db/local.db b/spec/test/db/local.db index a2bf9c0..06c9600 100644 Binary files a/spec/test/db/local.db and b/spec/test/db/local.db differ diff --git a/src/batch.d.ts b/src/batch.d.ts new file mode 100644 index 0000000..84c2594 --- /dev/null +++ b/src/batch.d.ts @@ -0,0 +1,18 @@ +import {RequestHandler, Router} from 'express'; + +export declare interface BatchRequestMessage { + id: string; + method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; + url: string; + headers?: Headers; + body?: unknown; + atomicityGroup?: string; + dependsOn?: string[]; +} + +export declare interface BatchRequestBody { + requests: BatchRequestMessage[]; +} + + +export declare function batch(router: Router): RequestHandler; \ No newline at end of file diff --git a/src/batch.js b/src/batch.js new file mode 100644 index 0000000..fc5010f --- /dev/null +++ b/src/batch.js @@ -0,0 +1,30 @@ +/** + * @param {import('Express').Router} router + * @returns {import('express').RequestHandler} + */ +function batch(router) { + return async function(req, res, next) { + try { + /** + * @type {Array} + */ + const requests = (req.body && req.body.requests) || []; + for (const request of requests) { + request.headers = request.headers || {}; + const authorization = req.headers['authorization']; + Object.assign(request.headers, { + authorization + }); + const uri = new URL(request.url, req.protocol + '://' + req.get('host')); + const subReq = req.clone(); + } + + } catch (e) { + next(e); + } + } +} + +export { + batch +} \ No newline at end of file diff --git a/src/service.js b/src/service.js index 7feeb39..537e55b 100644 --- a/src/service.js +++ b/src/service.js @@ -18,6 +18,7 @@ import {postEntitySetFunction} from './middleware'; import {postEntitySetAction} from './middleware'; import {getEntityFunction} from './middleware'; import {postEntityAction} from './middleware'; +import {batch} from './batch'; /** * @@ -90,6 +91,8 @@ serviceRouter.post('/:entitySet/:id', bindEntitySet(), postEntity()); /* DELETE /:entitySet/:id deletes a data object by id. */ serviceRouter.delete('/:entitySet/:id', bindEntitySet(), deleteEntity()); +serviceRouter.post('/\\$batch', batch(serviceRouter)); + Object.defineProperty(serviceRouter, 'alternateName', { configurable: true, enumerable: true,