@squidcloud/local-backend 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +6 -0
- package/.prettierrc +4 -0
- package/README.md +73 -0
- package/nest-cli.json +8 -0
- package/package.json +71 -0
- package/src/index.ts +2 -0
- package/src/local-backend.app.ts +15 -0
- package/src/local-backend.controller.ts +19 -0
- package/src/local-backend.module.ts +49 -0
- package/src/local-backend.service.ts +126 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +21 -0
package/.eslintrc.js
ADDED
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
|
6
|
+
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
|
7
|
+
|
|
8
|
+
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
|
11
|
+
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
|
12
|
+
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
|
13
|
+
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
|
14
|
+
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
|
15
|
+
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
|
16
|
+
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
|
17
|
+
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
|
18
|
+
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
|
19
|
+
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
|
20
|
+
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
|
21
|
+
</p>
|
|
22
|
+
<!--[](https://opencollective.com/nest#backer)
|
|
23
|
+
[](https://opencollective.com/nest#sponsor)-->
|
|
24
|
+
|
|
25
|
+
## Description
|
|
26
|
+
|
|
27
|
+
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
$ npm install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Running the app
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# development
|
|
39
|
+
$ npm run start
|
|
40
|
+
|
|
41
|
+
# watch mode
|
|
42
|
+
$ npm run start:dev
|
|
43
|
+
|
|
44
|
+
# production mode
|
|
45
|
+
$ npm run start:prod
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Test
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# unit tests
|
|
52
|
+
$ npm run test
|
|
53
|
+
|
|
54
|
+
# e2e tests
|
|
55
|
+
$ npm run test:e2e
|
|
56
|
+
|
|
57
|
+
# test coverage
|
|
58
|
+
$ npm run test:cov
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Support
|
|
62
|
+
|
|
63
|
+
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
|
64
|
+
|
|
65
|
+
## Stay in touch
|
|
66
|
+
|
|
67
|
+
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
|
68
|
+
- Website - [https://nestjs.com](https://nestjs.com/)
|
|
69
|
+
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
Nest is [MIT licensed](LICENSE).
|
package/nest-cli.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@squidcloud/local-backend",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"author": "Nir Peled",
|
|
6
|
+
"private": false,
|
|
7
|
+
"license": "UNLICENSED",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "nest build",
|
|
10
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
11
|
+
"start": "nest start",
|
|
12
|
+
"start:dev": "nest start --watch",
|
|
13
|
+
"start:debug": "nest start --debug --watch",
|
|
14
|
+
"start:prod": "node dist/main",
|
|
15
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
16
|
+
"test": "jest",
|
|
17
|
+
"test:watch": "jest --watch",
|
|
18
|
+
"test:cov": "jest --coverage",
|
|
19
|
+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
20
|
+
"test:e2e": "jest --config ./test/jest-e2e.json",
|
|
21
|
+
"publish:public": "npm run build && npm version patch && npm publish --access public"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@nestjs/common": "^9.0.0",
|
|
25
|
+
"@nestjs/core": "^9.0.0",
|
|
26
|
+
"@nestjs/platform-express": "^9.0.0",
|
|
27
|
+
"reflect-metadata": "^0.1.13",
|
|
28
|
+
"rxjs": "^7.2.0",
|
|
29
|
+
"@supercharge/promise-pool": "^2.4.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@nestjs/cli": "^9.0.0",
|
|
33
|
+
"@nestjs/schematics": "^9.0.0",
|
|
34
|
+
"@nestjs/testing": "^9.0.0",
|
|
35
|
+
"@types/express": "^4.17.13",
|
|
36
|
+
"@types/jest": "29.2.4",
|
|
37
|
+
"@types/node": "18.11.18",
|
|
38
|
+
"@types/supertest": "^2.0.11",
|
|
39
|
+
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
|
40
|
+
"@typescript-eslint/parser": "^5.0.0",
|
|
41
|
+
"eslint": "^8.0.1",
|
|
42
|
+
"eslint-config-prettier": "^8.3.0",
|
|
43
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
44
|
+
"jest": "29.3.1",
|
|
45
|
+
"prettier": "^2.3.2",
|
|
46
|
+
"source-map-support": "^0.5.20",
|
|
47
|
+
"supertest": "^6.1.3",
|
|
48
|
+
"ts-jest": "29.0.3",
|
|
49
|
+
"ts-loader": "^9.2.3",
|
|
50
|
+
"ts-node": "^10.0.0",
|
|
51
|
+
"tsconfig-paths": "4.1.1",
|
|
52
|
+
"typescript": "^4.7.4"
|
|
53
|
+
},
|
|
54
|
+
"jest": {
|
|
55
|
+
"moduleFileExtensions": [
|
|
56
|
+
"js",
|
|
57
|
+
"json",
|
|
58
|
+
"ts"
|
|
59
|
+
],
|
|
60
|
+
"rootDir": "src",
|
|
61
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
62
|
+
"transform": {
|
|
63
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
64
|
+
},
|
|
65
|
+
"collectCoverageFrom": [
|
|
66
|
+
"**/*.(t|j)s"
|
|
67
|
+
],
|
|
68
|
+
"coverageDirectory": "../coverage",
|
|
69
|
+
"testEnvironment": "node"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BodyInterceptor, LocalBackendModule } from './local-backend.module';
|
|
2
|
+
import { truthy } from '@squidcloud/common';
|
|
3
|
+
import { NestExpressApplication } from '@nestjs/platform-express';
|
|
4
|
+
import { NestFactory } from '@nestjs/core';
|
|
5
|
+
|
|
6
|
+
export async function bootstrapLocalBackend(
|
|
7
|
+
module: LocalBackendModule,
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
const app = await NestFactory.create<NestExpressApplication>(module, {
|
|
10
|
+
rawBody: true,
|
|
11
|
+
});
|
|
12
|
+
app.useBodyParser('json', { limit: '50mb' });
|
|
13
|
+
app.useGlobalInterceptors(truthy(app.get(BodyInterceptor)));
|
|
14
|
+
await app.listen(8020);
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Body, Controller, Get, Post, Res } from '@nestjs/common';
|
|
2
|
+
import { LocalBackendService } from './local-backend.service';
|
|
3
|
+
import { Response } from 'express';
|
|
4
|
+
|
|
5
|
+
@Controller('code')
|
|
6
|
+
export class LocalBackendController {
|
|
7
|
+
constructor(private readonly localBackendService: LocalBackendService) {}
|
|
8
|
+
|
|
9
|
+
@Post('run')
|
|
10
|
+
async runCode(@Body() request: any, @Res() res: Response): Promise<void> {
|
|
11
|
+
await this.localBackendService.processRunCode(request, res);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Get('health')
|
|
15
|
+
async healthCheck(@Res() res: Response): Promise<void> {
|
|
16
|
+
console.log('Got health check request');
|
|
17
|
+
res.status(200).send('OK');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CallHandler,
|
|
3
|
+
DynamicModule,
|
|
4
|
+
ExecutionContext,
|
|
5
|
+
Injectable,
|
|
6
|
+
Module,
|
|
7
|
+
NestInterceptor,
|
|
8
|
+
} from '@nestjs/common';
|
|
9
|
+
import { Observable } from 'rxjs';
|
|
10
|
+
import { deserializeObj } from '@squidcloud/common';
|
|
11
|
+
import { LocalBackendService } from './local-backend.service';
|
|
12
|
+
import { LocalBackendController } from './local-backend.controller';
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class BodyInterceptor implements NestInterceptor {
|
|
16
|
+
async intercept(
|
|
17
|
+
ctx: ExecutionContext,
|
|
18
|
+
next: CallHandler,
|
|
19
|
+
): Promise<Observable<any>> {
|
|
20
|
+
if (ctx.getType() !== 'http') return next.handle();
|
|
21
|
+
const httpRequest = ctx.switchToHttp().getRequest();
|
|
22
|
+
httpRequest.body = httpRequest.rawBody
|
|
23
|
+
? deserializeObj(httpRequest.rawBody.toString())
|
|
24
|
+
: {};
|
|
25
|
+
return next.handle();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Module({
|
|
30
|
+
imports: [],
|
|
31
|
+
controllers: [LocalBackendController],
|
|
32
|
+
providers: [LocalBackendService, BodyInterceptor],
|
|
33
|
+
})
|
|
34
|
+
export class LocalBackendModule {
|
|
35
|
+
static forRoot(backendService: any): DynamicModule {
|
|
36
|
+
const providers = [
|
|
37
|
+
{
|
|
38
|
+
provide: 'BACKEND_SERVICE',
|
|
39
|
+
useValue: backendService,
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
return {
|
|
43
|
+
module: LocalBackendModule,
|
|
44
|
+
global: true,
|
|
45
|
+
providers: providers,
|
|
46
|
+
exports: providers,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ActionRequest,
|
|
5
|
+
ExecuteFunctionPayload,
|
|
6
|
+
SecurityResponse,
|
|
7
|
+
serializeObj,
|
|
8
|
+
transformParams,
|
|
9
|
+
transformResponse,
|
|
10
|
+
truthy,
|
|
11
|
+
} from '@squidcloud/common';
|
|
12
|
+
import { PromisePool } from '@supercharge/promise-pool';
|
|
13
|
+
import { Response } from 'express';
|
|
14
|
+
|
|
15
|
+
@Injectable()
|
|
16
|
+
export class LocalBackendService {
|
|
17
|
+
constructor(
|
|
18
|
+
@Inject('BACKEND_SERVICE') private readonly backendService: any,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
private async executeActionRequest(
|
|
22
|
+
actionRequest: ActionRequest,
|
|
23
|
+
): Promise<any> {
|
|
24
|
+
const { action } = actionRequest;
|
|
25
|
+
if (action !== 'executeFunction') {
|
|
26
|
+
console.error('UNSUPPORTED ACTION!!! ', action);
|
|
27
|
+
return { ok: false, error: 'INVALID_ACTION' };
|
|
28
|
+
}
|
|
29
|
+
const payload = actionRequest.payload as ExecuteFunctionPayload;
|
|
30
|
+
const [serviceName, fnName] = payload.functionName.split(':');
|
|
31
|
+
let fn;
|
|
32
|
+
if (serviceName === 'default') {
|
|
33
|
+
fn = this.backendService[serviceName][fnName];
|
|
34
|
+
} else {
|
|
35
|
+
const Service = this.backendService[serviceName];
|
|
36
|
+
const service = new Service({
|
|
37
|
+
secrets: payload.secrets,
|
|
38
|
+
context: payload.context,
|
|
39
|
+
auth: payload.auth,
|
|
40
|
+
backendApiKey: payload.backendApiKey,
|
|
41
|
+
});
|
|
42
|
+
fn = service[fnName].bind(service);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!fn) {
|
|
46
|
+
return { ok: false, error: 'FUNCTION_NOT_FOUND' };
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const transformedParams = transformParams(
|
|
50
|
+
payload.params,
|
|
51
|
+
payload.executeFunctionAnnotationType,
|
|
52
|
+
);
|
|
53
|
+
const functionResponse = await fn(...transformedParams);
|
|
54
|
+
const transformedResponse = transformResponse(
|
|
55
|
+
functionResponse,
|
|
56
|
+
payload.executeFunctionAnnotationType,
|
|
57
|
+
);
|
|
58
|
+
return { ok: true, functionResponse: transformedResponse };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error('Error while invoking function', err);
|
|
61
|
+
return { ok: false, error: 'FUNCTION_ERROR', details: err.message };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async processRunCode(batchRequest: any, res: Response): Promise<void> {
|
|
66
|
+
const secrets = truthy(batchRequest.secrets, 'Secrets are required');
|
|
67
|
+
const responses: Array<any> = Array(batchRequest.requests.length);
|
|
68
|
+
let stop = false;
|
|
69
|
+
let errored = false;
|
|
70
|
+
await PromisePool.for(batchRequest.requests)
|
|
71
|
+
.withConcurrency(10)
|
|
72
|
+
.process(async (request: any, index) => {
|
|
73
|
+
if (stop) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const payload: ExecuteFunctionPayload = {
|
|
78
|
+
secrets: secrets.custom,
|
|
79
|
+
backendApiKey: secrets.backendApiKey,
|
|
80
|
+
functionName: request.functionName,
|
|
81
|
+
params: request.params,
|
|
82
|
+
auth: batchRequest.auth,
|
|
83
|
+
context: batchRequest.context,
|
|
84
|
+
codeDir: process.cwd(),
|
|
85
|
+
executeFunctionAnnotationType:
|
|
86
|
+
batchRequest.executeFunctionAnnotationType,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const response = await this.executeActionRequest({
|
|
90
|
+
action: 'executeFunction',
|
|
91
|
+
payload,
|
|
92
|
+
});
|
|
93
|
+
// noinspection DuplicatedCode
|
|
94
|
+
if (stop) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (
|
|
98
|
+
!response.ok ||
|
|
99
|
+
(batchRequest.configuration.isSecurityRules &&
|
|
100
|
+
!(response.functionResponse as SecurityResponse).rulesPassed)
|
|
101
|
+
) {
|
|
102
|
+
errored = errored || !response.ok;
|
|
103
|
+
stop = true;
|
|
104
|
+
responses.splice(0);
|
|
105
|
+
responses.push(response);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
responses[index] = response;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
errored = true;
|
|
111
|
+
stop = true;
|
|
112
|
+
responses.splice(0);
|
|
113
|
+
responses.push({
|
|
114
|
+
ok: false,
|
|
115
|
+
error: e.message,
|
|
116
|
+
functionName: request.functionName,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
res
|
|
122
|
+
.set('Content-Type', 'application/json')
|
|
123
|
+
.status(errored ? 500 : 200)
|
|
124
|
+
.send(serializeObj(responses));
|
|
125
|
+
}
|
|
126
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"removeComments": true,
|
|
6
|
+
"emitDecoratorMetadata": true,
|
|
7
|
+
"experimentalDecorators": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"target": "es2017",
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"baseUrl": "./",
|
|
13
|
+
"incremental": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"strictNullChecks": false,
|
|
16
|
+
"noImplicitAny": false,
|
|
17
|
+
"strictBindCallApply": false,
|
|
18
|
+
"forceConsistentCasingInFileNames": false,
|
|
19
|
+
"noFallthroughCasesInSwitch": false
|
|
20
|
+
}
|
|
21
|
+
}
|