mirror of
https://github.com/noodlapp/noodl-cloudservice.git
synced 2026-03-08 01:53:27 +01:00
feat: Upgrade to TypeScript (#4)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -118,3 +118,5 @@ out
|
|||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env*
|
.env*
|
||||||
|
|
||||||
|
packages/noodl-cloudservice/dist
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ FROM nikolaik/python-nodejs:python3.8-nodejs16
|
|||||||
WORKDIR /usr/src/noodl-cloudservice
|
WORKDIR /usr/src/noodl-cloudservice
|
||||||
COPY ./packages/noodl-cloudservice .
|
COPY ./packages/noodl-cloudservice .
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
COPY packages/noodl-cloudservice-docker .
|
COPY packages/noodl-cloudservice-docker .
|
||||||
RUN npm install
|
RUN npm install --install-links
|
||||||
|
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"spec_dir": "spec",
|
|
||||||
"spec_files": [
|
|
||||||
"**/*[sS]pec.js"
|
|
||||||
],
|
|
||||||
"helpers": [
|
|
||||||
"helpers/**/*.js"
|
|
||||||
],
|
|
||||||
"stopSpecOnExpectationFailure": false,
|
|
||||||
"random": false
|
|
||||||
}
|
|
||||||
314
packages/noodl-cloudservice/package-lock.json
generated
314
packages/noodl-cloudservice/package-lock.json
generated
@@ -14,6 +14,10 @@
|
|||||||
"parse-server": "^4.10.4",
|
"parse-server": "^4.10.4",
|
||||||
"parse-server-gcs-adapter": "git+https://github.com/noodlapp/noodl-parse-server-gcs-adapter.git",
|
"parse-server-gcs-adapter": "git+https://github.com/noodlapp/noodl-parse-server-gcs-adapter.git",
|
||||||
"winston-mongodb": "^5.1.0"
|
"winston-mongodb": "^5.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@apollo/protobufjs": {
|
"node_modules/@apollo/protobufjs": {
|
||||||
@@ -128,6 +132,18 @@
|
|||||||
"node": ">=0.1.90"
|
"node": ">=0.1.90"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/trace-mapping": "0.3.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@dabh/diagnostics": {
|
"node_modules/@dabh/diagnostics": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||||
@@ -588,6 +604,31 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz",
|
||||||
"integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg=="
|
"integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.4.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||||
|
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||||
|
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@node-rs/bcrypt": {
|
"node_modules/@node-rs/bcrypt": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt/-/bcrypt-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt/-/bcrypt-0.4.1.tgz",
|
||||||
@@ -822,6 +863,30 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tsconfig/node10": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@tsconfig/node12": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@tsconfig/node14": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@tsconfig/node16": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/accepts": {
|
"node_modules/@types/accepts": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
||||||
@@ -1077,6 +1142,27 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/acorn": {
|
||||||
|
"version": "8.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||||
|
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"acorn": "bin/acorn"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn-walk": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
@@ -1515,6 +1601,12 @@
|
|||||||
"readable-stream": "^2.0.6"
|
"readable-stream": "^2.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/arg": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/array-buffer-byte-length": {
|
"node_modules/array-buffer-byte-length": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
|
||||||
@@ -2058,6 +2150,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/create-require": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/cross-fetch": {
|
"node_modules/cross-fetch": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz",
|
||||||
@@ -2254,6 +2352,15 @@
|
|||||||
"node": ">=4.5.0"
|
"node": ">=4.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/diff": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/duplexify": {
|
"node_modules/duplexify": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
|
||||||
@@ -4068,6 +4175,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/make-error": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||||
|
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@@ -6094,6 +6207,49 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-node": {
|
||||||
|
"version": "10.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||||
|
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
|
"@tsconfig/node10": "^1.0.7",
|
||||||
|
"@tsconfig/node12": "^1.0.7",
|
||||||
|
"@tsconfig/node14": "^1.0.0",
|
||||||
|
"@tsconfig/node16": "^1.0.2",
|
||||||
|
"acorn": "^8.4.1",
|
||||||
|
"acorn-walk": "^8.1.1",
|
||||||
|
"arg": "^4.1.0",
|
||||||
|
"create-require": "^1.1.0",
|
||||||
|
"diff": "^4.0.1",
|
||||||
|
"make-error": "^1.1.1",
|
||||||
|
"v8-compile-cache-lib": "^3.0.1",
|
||||||
|
"yn": "3.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"ts-node": "dist/bin.js",
|
||||||
|
"ts-node-cwd": "dist/bin-cwd.js",
|
||||||
|
"ts-node-esm": "dist/bin-esm.js",
|
||||||
|
"ts-node-script": "dist/bin-script.js",
|
||||||
|
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||||
|
"ts-script": "dist/bin-script-deprecated.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@swc/core": ">=1.2.50",
|
||||||
|
"@swc/wasm": ">=1.2.50",
|
||||||
|
"@types/node": "*",
|
||||||
|
"typescript": ">=2.7"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@swc/core": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@swc/wasm": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||||
@@ -6223,6 +6379,19 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||||
@@ -6313,6 +6482,12 @@
|
|||||||
"uuid": "dist/bin/uuid"
|
"uuid": "dist/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/v8-compile-cache-lib": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/value-or-promise": {
|
"node_modules/value-or-promise": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz",
|
||||||
@@ -6650,6 +6825,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||||
},
|
},
|
||||||
|
"node_modules/yn": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
@@ -6767,6 +6951,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||||
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="
|
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="
|
||||||
},
|
},
|
||||||
|
"@cspotcode/source-map-support": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/trace-mapping": "0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@dabh/diagnostics": {
|
"@dabh/diagnostics": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||||
@@ -7135,6 +7328,28 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz",
|
||||||
"integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg=="
|
"integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg=="
|
||||||
},
|
},
|
||||||
|
"@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.4.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||||
|
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||||
|
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@node-rs/bcrypt": {
|
"@node-rs/bcrypt": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt/-/bcrypt-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt/-/bcrypt-0.4.1.tgz",
|
||||||
@@ -7326,6 +7541,30 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||||
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
|
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
|
||||||
},
|
},
|
||||||
|
"@tsconfig/node10": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@tsconfig/node12": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@tsconfig/node14": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@tsconfig/node16": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/accepts": {
|
"@types/accepts": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
||||||
@@ -7574,6 +7813,18 @@
|
|||||||
"negotiator": "0.6.3"
|
"negotiator": "0.6.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"acorn": {
|
||||||
|
"version": "8.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||||
|
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"acorn-walk": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"agent-base": {
|
"agent-base": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
@@ -7907,6 +8158,12 @@
|
|||||||
"readable-stream": "^2.0.6"
|
"readable-stream": "^2.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"arg": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"array-buffer-byte-length": {
|
"array-buffer-byte-length": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
|
||||||
@@ -8335,6 +8592,12 @@
|
|||||||
"vary": "^1"
|
"vary": "^1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"create-require": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"cross-fetch": {
|
"cross-fetch": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz",
|
||||||
@@ -8486,6 +8749,12 @@
|
|||||||
"streamsearch": "0.1.2"
|
"streamsearch": "0.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"diff": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"duplexify": {
|
"duplexify": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
|
||||||
@@ -9930,6 +10199,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"make-error": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||||
|
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"media-typer": {
|
"media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@@ -11473,6 +11748,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ts-node": {
|
||||||
|
"version": "10.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||||
|
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
|
"@tsconfig/node10": "^1.0.7",
|
||||||
|
"@tsconfig/node12": "^1.0.7",
|
||||||
|
"@tsconfig/node14": "^1.0.0",
|
||||||
|
"@tsconfig/node16": "^1.0.2",
|
||||||
|
"acorn": "^8.4.1",
|
||||||
|
"acorn-walk": "^8.1.1",
|
||||||
|
"arg": "^4.1.0",
|
||||||
|
"create-require": "^1.1.0",
|
||||||
|
"diff": "^4.0.1",
|
||||||
|
"make-error": "^1.1.1",
|
||||||
|
"v8-compile-cache-lib": "^3.0.1",
|
||||||
|
"yn": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tslib": {
|
"tslib": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||||
@@ -11566,6 +11862,12 @@
|
|||||||
"is-typed-array": "^1.1.9"
|
"is-typed-array": "^1.1.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"unbox-primitive": {
|
"unbox-primitive": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||||
@@ -11642,6 +11944,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
|
||||||
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
|
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
|
||||||
},
|
},
|
||||||
|
"v8-compile-cache-lib": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"value-or-promise": {
|
"value-or-promise": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz",
|
||||||
@@ -11897,6 +12205,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||||
},
|
},
|
||||||
|
"yn": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"yocto-queue": {
|
"yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|||||||
@@ -5,12 +5,27 @@
|
|||||||
"author": "Noodl <info@noodl.net>",
|
"author": "Noodl <info@noodl.net>",
|
||||||
"homepage": "https://noodl.net",
|
"homepage": "https://noodl.net",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./src/index.js",
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"type": "commonjs",
|
||||||
|
"scripts": {
|
||||||
|
"build:clean": "ts-node ./scripts/clean.ts",
|
||||||
|
"build:ts": "tsc",
|
||||||
|
"build:static": "ts-node ./scripts/copyStaticAssets.ts",
|
||||||
|
"build": "npm run build:clean && npm run build:ts && npm run build:static"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isolated-vm": "^4.4.2",
|
"isolated-vm": "^4.4.2",
|
||||||
"node-fetch": "2.6.7",
|
"node-fetch": "2.6.7",
|
||||||
"parse-server": "^4.10.4",
|
"parse-server": "^4.10.4",
|
||||||
"parse-server-gcs-adapter": "git+https://github.com/noodlapp/noodl-parse-server-gcs-adapter.git",
|
"parse-server-gcs-adapter": "git+https://github.com/noodlapp/noodl-parse-server-gcs-adapter.git",
|
||||||
"winston-mongodb": "^5.1.0"
|
"winston-mongodb": "^5.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"ts-node": "^10.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
packages/noodl-cloudservice/scripts/clean.ts
Normal file
5
packages/noodl-cloudservice/scripts/clean.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
const dist = path.join(__dirname, "../dist");
|
||||||
|
fs.rmSync(dist, { recursive: true, force: true });
|
||||||
11
packages/noodl-cloudservice/scripts/copyStaticAssets.ts
Normal file
11
packages/noodl-cloudservice/scripts/copyStaticAssets.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
const dist = path.join(__dirname, "../dist/static");
|
||||||
|
fs.mkdirSync(dist)
|
||||||
|
|
||||||
|
fs.cpSync(
|
||||||
|
path.join(__dirname, "../static"),
|
||||||
|
dist,
|
||||||
|
{ recursive: true }
|
||||||
|
);
|
||||||
@@ -1,408 +0,0 @@
|
|||||||
const fetch = require("node-fetch");
|
|
||||||
const ivm = require("isolated-vm");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
// Create a snapshot of a given runtime if needed
|
|
||||||
// of serve from the cache
|
|
||||||
const snapshots = {};
|
|
||||||
async function getRuntimeSnapshot(url) {
|
|
||||||
if (snapshots[url]) {
|
|
||||||
try {
|
|
||||||
await snapshots[url];
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Disposing runtime snapshot due to error in create: `, e);
|
|
||||||
delete snapshots[url];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshots[url]) return snapshots[url];
|
|
||||||
else
|
|
||||||
return (snapshots[url] = (async () => {
|
|
||||||
console.log("- Loading runtime script");
|
|
||||||
const res = await fetch(url);
|
|
||||||
const script = await res.text();
|
|
||||||
|
|
||||||
return ivm.Isolate.createSnapshot([
|
|
||||||
{
|
|
||||||
code: `var _noodl_handleReq, _noodl_api_response,_noodl_process_jobs;`,
|
|
||||||
}, // Must declare, otherwise we will get error when trying to set as global from function
|
|
||||||
{ code: script },
|
|
||||||
]);
|
|
||||||
})());
|
|
||||||
}
|
|
||||||
|
|
||||||
const _defaultRuntime = process.env.NOODL_DEFAULT_CLOUD_RUNTIME;
|
|
||||||
|
|
||||||
// Create an isolated context for a specific environment
|
|
||||||
async function createContext(env) {
|
|
||||||
if (env.version === undefined) {
|
|
||||||
throw Error("No version specified when creating context.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeOut = 15;
|
|
||||||
const memoryLimit = env.memoryLimit || 128;
|
|
||||||
|
|
||||||
// Load custom code
|
|
||||||
console.log("Creating context for version " + env.version);
|
|
||||||
console.log("- Loading cloud deploy");
|
|
||||||
const res = await fetch(
|
|
||||||
env.backendEndpoint +
|
|
||||||
'/classes/Ndl_CF?where={"version":"' +
|
|
||||||
env.version +
|
|
||||||
'"}',
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"X-Parse-Application-Id": env.appId,
|
|
||||||
"X-Parse-Master-Key": env.masterKey,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
let code = "",
|
|
||||||
cloudRuntime;
|
|
||||||
if (data.results && data.results.length > 0) {
|
|
||||||
data.results.sort((a, b) => a._created_at - b._created_at);
|
|
||||||
|
|
||||||
cloudRuntime = data.results[0].runtime;
|
|
||||||
|
|
||||||
data.results.forEach((d) => {
|
|
||||||
code += d.code;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw Error(
|
|
||||||
`No cloud functions found for env ${env.appId} and version ${env.version}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("- Starting up isolate");
|
|
||||||
let runtime = cloudRuntime || _defaultRuntime;
|
|
||||||
if (!runtime.endsWith(".js")) runtime = runtime + ".js";
|
|
||||||
|
|
||||||
console.log("- Using runtime: " + runtime);
|
|
||||||
const snapshot = await getRuntimeSnapshot(
|
|
||||||
(process.env.NOODL_CLOUD_RUNTIMES_LOCATION ||
|
|
||||||
"https://runtimes.noodl.cloud") +
|
|
||||||
"/" +
|
|
||||||
runtime
|
|
||||||
);
|
|
||||||
|
|
||||||
const isolate = new ivm.Isolate({ memoryLimit, snapshot });
|
|
||||||
const context = await isolate.createContext();
|
|
||||||
|
|
||||||
const jail = context.global;
|
|
||||||
|
|
||||||
// Bootstrap message handler
|
|
||||||
jail.setSync("global", context.global.derefInto());
|
|
||||||
|
|
||||||
// ---------------- API ----------------
|
|
||||||
let ongoingAPICalls = 0;
|
|
||||||
const maxOngoingAPICalls = 100;
|
|
||||||
|
|
||||||
function _internalServerError(message) {
|
|
||||||
Object.keys(responseHandlers).forEach((k) => {
|
|
||||||
if (typeof responseHandlers[k] === "function") {
|
|
||||||
responseHandlers[k]({
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: message || "Internal server error" }),
|
|
||||||
});
|
|
||||||
delete responseHandlers[k];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function _eval(script) {
|
|
||||||
if (isolate.isDisposed) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await context.eval(script, { timeout: timeOut * 1000 });
|
|
||||||
} catch (e) {
|
|
||||||
console.log("_eval", e);
|
|
||||||
if (
|
|
||||||
e.message ===
|
|
||||||
"Isolate was disposed during execution due to memory limit"
|
|
||||||
) {
|
|
||||||
// Isolate was disposed, return out of memory error for all pending requests
|
|
||||||
_internalServerError("Out of memory");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isolate.isDisposed) {
|
|
||||||
// The isolate was disposed, end all currently pending requests
|
|
||||||
_internalServerError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _api_respond(token, res) {
|
|
||||||
ongoingAPICalls--;
|
|
||||||
if (ongoingAPICalls < 0) ongoingAPICalls = 0;
|
|
||||||
|
|
||||||
if (token !== undefined)
|
|
||||||
_eval("_noodl_api_response('" + token + "'," + JSON.stringify(res) + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loggers
|
|
||||||
const logger = env.logger;
|
|
||||||
|
|
||||||
const apiFunctions = {
|
|
||||||
log: function (token, args) {
|
|
||||||
logger.log(
|
|
||||||
args.level || "info",
|
|
||||||
typeof args === "string" ? args : args.message
|
|
||||||
);
|
|
||||||
_api_respond(token);
|
|
||||||
},
|
|
||||||
fetch: function (token, args) {
|
|
||||||
fetch(args.url, args)
|
|
||||||
.then((r) => {
|
|
||||||
r.text()
|
|
||||||
.then((text) => {
|
|
||||||
_api_respond(token, {
|
|
||||||
ok: r.ok,
|
|
||||||
redirected: r.redirected,
|
|
||||||
statusText: r.statusText,
|
|
||||||
status: r.status,
|
|
||||||
headers: r.headers.raw(),
|
|
||||||
body: text,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
_api_respond(token, { error: e.message || true });
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
_api_respond(token, { error: e.message || true });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setTimeout: function (token, millis) {
|
|
||||||
setTimeout(() => {
|
|
||||||
_api_respond(token);
|
|
||||||
}, millis);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
jail.setSync("_noodl_api_call", function (functionName, token, args) {
|
|
||||||
ongoingAPICalls++;
|
|
||||||
|
|
||||||
if (!apiFunctions[functionName]) {
|
|
||||||
_api_respond(token, { error: "No such API function" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ongoingAPICalls >= maxOngoingAPICalls) {
|
|
||||||
// Protect against user code flooding API calls
|
|
||||||
_api_respond(token, { error: "Too many API calls" });
|
|
||||||
console.log("Warning too many concurrent ongoing api calls...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log('API Call: ' + functionName + ' with args ', args)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const _args = JSON.parse(JSON.stringify(args)); // extra safe
|
|
||||||
|
|
||||||
apiFunctions[functionName](token, _args);
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Warning failed to execute api function: ", e);
|
|
||||||
_api_respond(token, { error: "Failed to execute API call" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// event queue
|
|
||||||
let hasScheduledProcessJobs = false;
|
|
||||||
jail.setSync("_noodl_request_process_jobs", function () {
|
|
||||||
if (hasScheduledProcessJobs) return;
|
|
||||||
hasScheduledProcessJobs = true;
|
|
||||||
setImmediate(() => {
|
|
||||||
hasScheduledProcessJobs = false;
|
|
||||||
_eval("_noodl_process_jobs()");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Some cloud services related stuff
|
|
||||||
jail.setSync(
|
|
||||||
"_noodl_cloudservices",
|
|
||||||
{
|
|
||||||
masterKey: env.masterKey,
|
|
||||||
endpoint: env.backendEndpoint,
|
|
||||||
appId: env.appId,
|
|
||||||
},
|
|
||||||
{ copy: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Result from request
|
|
||||||
const responseHandlers = {};
|
|
||||||
jail.setSync("_noodl_response", function (token, args) {
|
|
||||||
if (typeof responseHandlers[token] === "function") {
|
|
||||||
responseHandlers[token](args);
|
|
||||||
delete responseHandlers[token];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const script = await isolate.compileScript(code);
|
|
||||||
await script.run(context, {
|
|
||||||
timeout: timeOut * 1000, // 15 s to initialize
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Failed when compiling and running cloud function code");
|
|
||||||
isolate.dispose();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _checkMemUsage() {
|
|
||||||
if (isolate.isDisposed) return; // Ignore already disposed isolate
|
|
||||||
|
|
||||||
const heap = isolate.getHeapStatisticsSync();
|
|
||||||
const memUsage = heap.total_heap_size / (1024 * 1024);
|
|
||||||
|
|
||||||
if (memUsage > memoryLimit * 0.8) {
|
|
||||||
// Mem usage has exceeded 80% of limit
|
|
||||||
// discard the context, a new context will be created for new incoming requests
|
|
||||||
// and this one will be cleaned up
|
|
||||||
const uri = env.appId + "/" + env.version;
|
|
||||||
if (!_context.markedToBeDiscarded) {
|
|
||||||
// Make sure it has not already been marked
|
|
||||||
_context.markedToBeDiscarded = true;
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Marking context ${uri} as to be discarded due to memory limit, will be discarded in 2 mins.`
|
|
||||||
);
|
|
||||||
contextCache[uri + "/discarded/" + Date.now()] =
|
|
||||||
Promise.resolve(_context);
|
|
||||||
_context.ttl = Date.now() + 2 * 60 * 1000; // Kill in 3 minutes
|
|
||||||
delete contextCache[uri];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleRequest(options) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
let hasResponded = false;
|
|
||||||
|
|
||||||
_context.ttl = Date.now() + 10 * 60 * 1000; // Keep context alive
|
|
||||||
|
|
||||||
const token = Math.random().toString(26).slice(2);
|
|
||||||
const _req = {
|
|
||||||
function: options.functionId,
|
|
||||||
headers: options.headers,
|
|
||||||
body: options.body, // just forward raw body
|
|
||||||
};
|
|
||||||
responseHandlers[token] = (_res) => {
|
|
||||||
if (hasResponded) return;
|
|
||||||
hasResponded = true;
|
|
||||||
_checkMemUsage();
|
|
||||||
|
|
||||||
resolve(_res);
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (hasResponded) return;
|
|
||||||
hasResponded = true;
|
|
||||||
_checkMemUsage();
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: "timeout" }),
|
|
||||||
});
|
|
||||||
}, timeOut * 1000); // Timeout if no reply from function
|
|
||||||
|
|
||||||
_eval(`_noodl_handleReq('${token}',${JSON.stringify(_req)})`)
|
|
||||||
.then(() => {
|
|
||||||
// All good
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
if (hasResponded) return;
|
|
||||||
hasResponded = true;
|
|
||||||
_checkMemUsage();
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: e.message }),
|
|
||||||
});
|
|
||||||
console.log("Error while running function:", e);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (hasResponded) return;
|
|
||||||
hasResponded = true;
|
|
||||||
_checkMemUsage();
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: e.message }),
|
|
||||||
});
|
|
||||||
console.log("Error while running function:", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const _context = {
|
|
||||||
context,
|
|
||||||
isolate,
|
|
||||||
responseHandlers,
|
|
||||||
version: env.version,
|
|
||||||
eval: _eval,
|
|
||||||
handleRequest,
|
|
||||||
ttl: Date.now() + 10 * 60 * 1000,
|
|
||||||
};
|
|
||||||
return _context;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contextCache = {};
|
|
||||||
async function getCachedContext(env) {
|
|
||||||
const uri = env.appId + "/" + env.version;
|
|
||||||
|
|
||||||
// Check if the isolate have been disposed
|
|
||||||
if (contextCache[uri]) {
|
|
||||||
let context;
|
|
||||||
try {
|
|
||||||
context = await contextCache[uri];
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Disposing context due to error in create: `, e);
|
|
||||||
delete contextCache[uri];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context && context.isolate && context.isolate.isDisposed)
|
|
||||||
delete contextCache[uri];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contextCache[uri]) {
|
|
||||||
return contextCache[uri];
|
|
||||||
} else {
|
|
||||||
return (contextCache[uri] = createContext(env));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasScheduledContextCachePurge = false;
|
|
||||||
function scheduleContextCachePurge() {
|
|
||||||
if (hasScheduledContextCachePurge) return;
|
|
||||||
hasScheduledContextCachePurge = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
hasScheduledContextCachePurge = false;
|
|
||||||
Object.keys(contextCache).forEach(async (k) => {
|
|
||||||
let context;
|
|
||||||
try {
|
|
||||||
context = await contextCache[k];
|
|
||||||
} catch (e) {
|
|
||||||
// This is a context that have failed to create
|
|
||||||
// delete it.
|
|
||||||
console.log(`Disposing isolate ${k} due to error in create: `, e);
|
|
||||||
delete contextCache[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context && context.isolate.isDisposed) {
|
|
||||||
console.log(`Disposing isolate ${k} due to "already disposed": `);
|
|
||||||
delete contextCache[k];
|
|
||||||
} else if (context && context.ttl < Date.now()) {
|
|
||||||
console.log(`Disposing isolate ${k} due to inactivity.`);
|
|
||||||
context.isolate.dispose();
|
|
||||||
delete contextCache[k];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 5 * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
scheduleContextCachePurge,
|
|
||||||
getCachedContext,
|
|
||||||
};
|
|
||||||
91
packages/noodl-cloudservice/src/cfcontext.ts
Normal file
91
packages/noodl-cloudservice/src/cfcontext.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import {
|
||||||
|
ContextGlobalState,
|
||||||
|
CreateContextEnv,
|
||||||
|
createContext,
|
||||||
|
} from "./context/createContext";
|
||||||
|
import { Logger } from "./logger";
|
||||||
|
import { CFVersion } from "./function-deploy";
|
||||||
|
|
||||||
|
const contextGlobal: ContextGlobalState = {
|
||||||
|
cache: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetCachedContextOptions = {
|
||||||
|
backendEndpoint: string;
|
||||||
|
appId: string;
|
||||||
|
masterKey: string;
|
||||||
|
version: CFVersion;
|
||||||
|
logger: Logger;
|
||||||
|
timeout: number | undefined;
|
||||||
|
memoryLimit: number | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getCachedContext(
|
||||||
|
options: GetCachedContextOptions
|
||||||
|
): ReturnType<typeof createContext> {
|
||||||
|
const uri = options.appId + "/" + options.version;
|
||||||
|
|
||||||
|
// Check if the isolate have been disposed
|
||||||
|
if (contextGlobal.cache[uri]) {
|
||||||
|
let context: Awaited<ReturnType<typeof createContext>>;
|
||||||
|
try {
|
||||||
|
context = await contextGlobal.cache[uri];
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Disposing context due to error in create: `, e);
|
||||||
|
delete contextGlobal.cache[uri];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && context.state.isolate && context.state.isolate.isDisposed) {
|
||||||
|
delete contextGlobal.cache[uri];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contextGlobal.cache[uri]) {
|
||||||
|
return contextGlobal.cache[uri];
|
||||||
|
} else {
|
||||||
|
const env: CreateContextEnv = {
|
||||||
|
version: options.version.functionVersion,
|
||||||
|
functionTimeout: options.timeout || 15,
|
||||||
|
initializeTimeout: options.timeout || 15,
|
||||||
|
memoryLimit: options.memoryLimit || 128,
|
||||||
|
backendEndpoint: options.backendEndpoint,
|
||||||
|
appId: options.appId,
|
||||||
|
masterKey: options.masterKey,
|
||||||
|
logger: options.logger,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createContextPromise = createContext(contextGlobal, env);
|
||||||
|
contextGlobal.cache[uri] = createContextPromise;
|
||||||
|
|
||||||
|
return createContextPromise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasScheduledContextCachePurge = false;
|
||||||
|
export function scheduleContextCachePurge() {
|
||||||
|
if (hasScheduledContextCachePurge) return;
|
||||||
|
hasScheduledContextCachePurge = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
hasScheduledContextCachePurge = false;
|
||||||
|
Object.keys(contextGlobal.cache).forEach(async (k) => {
|
||||||
|
let context: Awaited<ReturnType<typeof createContext>>;
|
||||||
|
try {
|
||||||
|
context = await contextGlobal.cache[k];
|
||||||
|
} catch (e) {
|
||||||
|
// This is a context that have failed to create
|
||||||
|
// delete it.
|
||||||
|
console.log(`Disposing isolate ${k} due to error in create: `, e);
|
||||||
|
delete contextGlobal.cache[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && context.state.isolate.isDisposed) {
|
||||||
|
console.log(`Disposing isolate ${k} due to "already disposed": `);
|
||||||
|
delete contextGlobal.cache[k];
|
||||||
|
} else if (context && context.state.ttl < Date.now()) {
|
||||||
|
console.log(`Disposing isolate ${k} due to inactivity.`);
|
||||||
|
context.state.isolate.dispose();
|
||||||
|
delete contextGlobal.cache[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 5 * 1000);
|
||||||
|
}
|
||||||
379
packages/noodl-cloudservice/src/context/createContext.ts
Normal file
379
packages/noodl-cloudservice/src/context/createContext.ts
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
import fetch from "node-fetch";
|
||||||
|
import ivm from "isolated-vm";
|
||||||
|
import { Logger } from "../logger";
|
||||||
|
import { getRuntimeSnapshot } from "./snapshot";
|
||||||
|
|
||||||
|
export interface ContextGlobalState {
|
||||||
|
cache: Record<string, ReturnType<typeof createContext>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateContextEnv {
|
||||||
|
/**
|
||||||
|
* The version is used to query the database for the Cloud Functions.
|
||||||
|
*
|
||||||
|
* Query: '/classes/Ndl_CF?where={"version":"${env.version}"}'
|
||||||
|
*/
|
||||||
|
version: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout if no reply from function.
|
||||||
|
* Recommend: 15
|
||||||
|
*/
|
||||||
|
functionTimeout: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timeout time to initialize a new isolate in seconds.
|
||||||
|
* Recommend: 15
|
||||||
|
*/
|
||||||
|
initializeTimeout: number;
|
||||||
|
|
||||||
|
memoryLimit: number; // Recommend: 128
|
||||||
|
backendEndpoint: string;
|
||||||
|
appId: string;
|
||||||
|
masterKey: string;
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextState {
|
||||||
|
global: ContextGlobalState;
|
||||||
|
env: CreateContextEnv;
|
||||||
|
context: ivm.Context;
|
||||||
|
isolate: ivm.Isolate;
|
||||||
|
markedToBeDiscarded: boolean;
|
||||||
|
ttl: number;
|
||||||
|
responseHandlers: Record<string, (req: unknown) => void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function internalServerError(state: ContextState, message: string | undefined) {
|
||||||
|
Object.keys(state.responseHandlers).forEach((k) => {
|
||||||
|
if (typeof state.responseHandlers[k] === "function") {
|
||||||
|
state.responseHandlers[k]({
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ error: message || "Internal server error" }),
|
||||||
|
});
|
||||||
|
delete state.responseHandlers[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function contextEval(state: ContextState, script: string) {
|
||||||
|
if (state.isolate.isDisposed) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await state.context.eval(script, { timeout: state.env.functionTimeout * 1000 });
|
||||||
|
} catch (e) {
|
||||||
|
console.log("contextEval", e);
|
||||||
|
if (
|
||||||
|
e.message ===
|
||||||
|
"Isolate was disposed during execution due to memory limit"
|
||||||
|
) {
|
||||||
|
// Isolate was disposed, return out of memory error for all pending requests
|
||||||
|
internalServerError(state, "Out of memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.isolate.isDisposed) {
|
||||||
|
// The isolate was disposed, end all currently pending requests
|
||||||
|
internalServerError(state, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMemUsage(state: ContextState) {
|
||||||
|
if (state.isolate.isDisposed) return; // Ignore already disposed isolate
|
||||||
|
|
||||||
|
const heap = state.isolate.getHeapStatisticsSync();
|
||||||
|
const memUsage = heap.total_heap_size / (1024 * 1024);
|
||||||
|
|
||||||
|
if (memUsage > state.env.memoryLimit * 0.8) {
|
||||||
|
// Mem usage has exceeded 80% of limit
|
||||||
|
// discard the context, a new context will be created for new incoming requests
|
||||||
|
// and this one will be cleaned up
|
||||||
|
const uri = state.env.appId + "/" + state.env.version;
|
||||||
|
if (!state.markedToBeDiscarded) {
|
||||||
|
// Make sure it has not already been marked
|
||||||
|
state.markedToBeDiscarded = true;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Marking context ${uri} as to be discarded due to memory limit, will be discarded in 2 mins.`
|
||||||
|
);
|
||||||
|
state.global.cache[uri + "/discarded/" + Date.now()] = state.global.cache[uri];
|
||||||
|
state.ttl = Date.now() + 2 * 60 * 1000; // Kill in 3 minutes
|
||||||
|
delete state.global.cache[uri];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HandleRequestOptions {
|
||||||
|
functionId: string;
|
||||||
|
headers: Record<string, unknown>;
|
||||||
|
body: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HandleRequestResult = {
|
||||||
|
headers?: Record<string, unknown>;
|
||||||
|
statusCode: number;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRequest(state: ContextState, options: HandleRequestOptions): Promise<HandleRequestResult> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let hasResponded = false;
|
||||||
|
|
||||||
|
const token = Math.random().toString(26).slice(2);
|
||||||
|
state.ttl = Date.now() + 10 * 60 * 1000; // Keep context alive
|
||||||
|
state.responseHandlers[token] = (response: HandleRequestResult) => {
|
||||||
|
if (hasResponded) return;
|
||||||
|
hasResponded = true;
|
||||||
|
checkMemUsage(state);
|
||||||
|
|
||||||
|
resolve(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const _req = {
|
||||||
|
function: options.functionId,
|
||||||
|
headers: options.headers,
|
||||||
|
body: options.body, // just forward the raw body
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Here is a race condition between setTimeout and contextEval.
|
||||||
|
// Where contextEval also have the same timeout.
|
||||||
|
setTimeout(() => {
|
||||||
|
if (hasResponded) return;
|
||||||
|
hasResponded = true;
|
||||||
|
checkMemUsage(state);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ error: "timeout" }),
|
||||||
|
});
|
||||||
|
}, state.env.functionTimeout * 1000);
|
||||||
|
|
||||||
|
contextEval(state, `_noodl_handleReq('${token}',${JSON.stringify(_req)})`)
|
||||||
|
.then(() => {
|
||||||
|
// All good
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (hasResponded) return;
|
||||||
|
hasResponded = true;
|
||||||
|
checkMemUsage(state);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ error: e.message }),
|
||||||
|
});
|
||||||
|
console.log("Error while running function:", e);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (hasResponded) return;
|
||||||
|
hasResponded = true;
|
||||||
|
checkMemUsage(state);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ error: e.message }),
|
||||||
|
});
|
||||||
|
console.log("Error while running function:", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an isolated context for a specific environment
|
||||||
|
* @param env
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function createContext(global: ContextGlobalState, env: CreateContextEnv) {
|
||||||
|
if (env.version === undefined) {
|
||||||
|
throw Error("No version specified when creating context.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load custom code
|
||||||
|
console.log("Creating context for version " + env.version);
|
||||||
|
console.log("- Loading cloud deploy");
|
||||||
|
const res = await fetch(
|
||||||
|
env.backendEndpoint +
|
||||||
|
'/classes/Ndl_CF?where={"version":"' +
|
||||||
|
env.version +
|
||||||
|
'"}',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"X-Parse-Application-Id": env.appId,
|
||||||
|
"X-Parse-Master-Key": env.masterKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
let code = "", cloudRuntime = "";
|
||||||
|
if (data.results && data.results.length > 0) {
|
||||||
|
data.results.sort((a, b) => a._created_at - b._created_at);
|
||||||
|
|
||||||
|
cloudRuntime = data.results[0].runtime;
|
||||||
|
|
||||||
|
data.results.forEach((d) => {
|
||||||
|
code += d.code;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw Error(
|
||||||
|
`No cloud functions found for env ${env.appId} and version ${env.version}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _defaultRuntime = process.env.NOODL_DEFAULT_CLOUD_RUNTIME;
|
||||||
|
let runtime = cloudRuntime || _defaultRuntime;
|
||||||
|
if (!runtime.endsWith(".js")) runtime = runtime + ".js";
|
||||||
|
|
||||||
|
console.log("- Using runtime: " + runtime);
|
||||||
|
const snapshot = await getRuntimeSnapshot(
|
||||||
|
(process.env.NOODL_CLOUD_RUNTIMES_LOCATION ||
|
||||||
|
"https://runtimes.noodl.cloud") +
|
||||||
|
"/" +
|
||||||
|
runtime
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("- Starting up isolate");
|
||||||
|
const isolate = new ivm.Isolate({ memoryLimit: env.memoryLimit, snapshot });
|
||||||
|
const context = await isolate.createContext();
|
||||||
|
|
||||||
|
const jail = context.global;
|
||||||
|
|
||||||
|
// Bootstrap message handler
|
||||||
|
jail.setSync("global", context.global.derefInto());
|
||||||
|
|
||||||
|
// ---------------- API ----------------
|
||||||
|
let ongoingAPICalls = 0;
|
||||||
|
const maxOngoingAPICalls = 100;
|
||||||
|
|
||||||
|
function _api_respond(token: string, res?) {
|
||||||
|
ongoingAPICalls--;
|
||||||
|
if (ongoingAPICalls < 0) ongoingAPICalls = 0;
|
||||||
|
|
||||||
|
if (token !== undefined) {
|
||||||
|
contextEval(state, "_noodl_api_response('" + token + "'," + JSON.stringify(res) + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loggers
|
||||||
|
const logger = env.logger;
|
||||||
|
|
||||||
|
const apiFunctions = {
|
||||||
|
log: function (token, args) {
|
||||||
|
logger.log(
|
||||||
|
args.level || "info",
|
||||||
|
typeof args === "string" ? args : args.message
|
||||||
|
);
|
||||||
|
_api_respond(token);
|
||||||
|
},
|
||||||
|
fetch: function (token, args) {
|
||||||
|
fetch(args.url, args)
|
||||||
|
.then((r) => {
|
||||||
|
r.text()
|
||||||
|
.then((text) => {
|
||||||
|
_api_respond(token, {
|
||||||
|
ok: r.ok,
|
||||||
|
redirected: r.redirected,
|
||||||
|
statusText: r.statusText,
|
||||||
|
status: r.status,
|
||||||
|
headers: r.headers.raw(),
|
||||||
|
body: text,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
_api_respond(token, { error: e.message || true });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
_api_respond(token, { error: e.message || true });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setTimeout: function (token, millis) {
|
||||||
|
setTimeout(() => {
|
||||||
|
_api_respond(token);
|
||||||
|
}, millis);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jail.setSync("_noodl_api_call", function (functionName, token, args) {
|
||||||
|
ongoingAPICalls++;
|
||||||
|
|
||||||
|
if (!apiFunctions[functionName]) {
|
||||||
|
_api_respond(token, { error: "No such API function" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ongoingAPICalls >= maxOngoingAPICalls) {
|
||||||
|
// Protect against user code flooding API calls
|
||||||
|
_api_respond(token, { error: "Too many API calls" });
|
||||||
|
console.log("Warning too many concurrent ongoing api calls...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('API Call: ' + functionName + ' with args ', args)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const _args = JSON.parse(JSON.stringify(args)); // extra safe
|
||||||
|
|
||||||
|
apiFunctions[functionName](token, _args);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Warning failed to execute api function: ", e);
|
||||||
|
_api_respond(token, { error: "Failed to execute API call" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// event queue
|
||||||
|
let hasScheduledProcessJobs = false;
|
||||||
|
jail.setSync("_noodl_request_process_jobs", function () {
|
||||||
|
if (hasScheduledProcessJobs) return;
|
||||||
|
hasScheduledProcessJobs = true;
|
||||||
|
setImmediate(() => {
|
||||||
|
hasScheduledProcessJobs = false;
|
||||||
|
contextEval(state, "_noodl_process_jobs()");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Some cloud services related stuff
|
||||||
|
jail.setSync(
|
||||||
|
"_noodl_cloudservices",
|
||||||
|
{
|
||||||
|
masterKey: env.masterKey,
|
||||||
|
endpoint: env.backendEndpoint,
|
||||||
|
appId: env.appId,
|
||||||
|
},
|
||||||
|
{ copy: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Result from request
|
||||||
|
jail.setSync("_noodl_response", function (token, args) {
|
||||||
|
if (typeof state.responseHandlers[token] === "function") {
|
||||||
|
state.responseHandlers[token](args);
|
||||||
|
delete state.responseHandlers[token];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const script = await isolate.compileScript(code);
|
||||||
|
await script.run(context, {
|
||||||
|
timeout: env.initializeTimeout * 1000, // 15 s to initialize
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed when compiling and running cloud function code");
|
||||||
|
isolate.dispose();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state: ContextState = {
|
||||||
|
global,
|
||||||
|
env,
|
||||||
|
context,
|
||||||
|
isolate,
|
||||||
|
markedToBeDiscarded: false,
|
||||||
|
ttl: Date.now() + 10 * 60 * 1000,
|
||||||
|
responseHandlers: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
eval: (script: string) => contextEval(state, script),
|
||||||
|
handleRequest: (options: HandleRequestOptions) => handleRequest(state, options),
|
||||||
|
};
|
||||||
|
}
|
||||||
40
packages/noodl-cloudservice/src/context/snapshot.ts
Normal file
40
packages/noodl-cloudservice/src/context/snapshot.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import fetch from "node-fetch";
|
||||||
|
import ivm from "isolated-vm";
|
||||||
|
|
||||||
|
// Create a snapshot of a given runtime if needed
|
||||||
|
// of serve from the cache
|
||||||
|
const snapshots: Record<string, Promise<ivm.ExternalCopy<ArrayBuffer>>> = {};
|
||||||
|
|
||||||
|
function createSnapshot(script: string) {
|
||||||
|
return ivm.Isolate.createSnapshot([
|
||||||
|
{
|
||||||
|
code: `var _noodl_handleReq, _noodl_api_response,_noodl_process_jobs;`,
|
||||||
|
}, // Must declare, otherwise we will get error when trying to set as global from function
|
||||||
|
{ code: script },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchRuntime(url: string) {
|
||||||
|
console.log("- Loading runtime script");
|
||||||
|
const res = await fetch(url);
|
||||||
|
const script = await res.text();
|
||||||
|
return createSnapshot(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRuntimeSnapshot(url: string) {
|
||||||
|
if (snapshots[url]) {
|
||||||
|
try {
|
||||||
|
await snapshots[url];
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Disposing runtime snapshot due to error in create: `, e);
|
||||||
|
delete snapshots[url];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshots[url]) {
|
||||||
|
return snapshots[url];
|
||||||
|
} else {
|
||||||
|
return snapshots[url] = fetchRuntime(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
const fetch = require('node-fetch');
|
|
||||||
|
|
||||||
// Get the latest version of cloud functions deploy, if not provided in header
|
|
||||||
async function getLatestVersion({ appId, masterKey }) {
|
|
||||||
const res = await fetch('http://localhost:' + port + '/classes/Ndl_CF?limit=1&order=-createdAt&keys=version', {
|
|
||||||
headers: {
|
|
||||||
'X-Parse-Application-Id': appId,
|
|
||||||
'X-Parse-Master-Key': masterKey
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if (json.results && json.results.length === 1)
|
|
||||||
return json.results[0].version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _latestVersionCache;
|
|
||||||
async function getLatestVersionCached(options) {
|
|
||||||
if (_latestVersionCache && (_latestVersionCache.ttl === undefined || _latestVersionCache.ttl > Date.now())) {
|
|
||||||
return _latestVersionCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const latestVersion = await getLatestVersion(options);
|
|
||||||
_latestVersionCache = latestVersion;
|
|
||||||
_latestVersionCache.ttl = Date.now() + 15 * 1000; // Cache for 15s
|
|
||||||
} catch {
|
|
||||||
_latestVersionCache = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _randomString(size) {
|
|
||||||
if (size === 0) {
|
|
||||||
throw new Error("Zero-length randomString is useless.");
|
|
||||||
}
|
|
||||||
const chars =
|
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789";
|
|
||||||
let objectId = "";
|
|
||||||
for (let i = 0; i < size; ++i) {
|
|
||||||
objectId += chars[Math.floor((1 + Math.random()) * 0x10000) % chars.length];
|
|
||||||
}
|
|
||||||
return objectId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function chunkDeploy(str, size) {
|
|
||||||
const numChunks = Math.ceil(str.length / size)
|
|
||||||
const chunks = new Array(numChunks)
|
|
||||||
|
|
||||||
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
|
|
||||||
chunks[i] = str.substr(o, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunks
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deployFunctions({
|
|
||||||
port,
|
|
||||||
appId,
|
|
||||||
masterKey,
|
|
||||||
runtime,
|
|
||||||
data
|
|
||||||
}) {
|
|
||||||
const deploy = "const _exportedComponents = " + data
|
|
||||||
const version = _randomString(16)
|
|
||||||
|
|
||||||
// Split deploy into 100kb sizes
|
|
||||||
const chunks = chunkDeploy(deploy, 100 * 1024);
|
|
||||||
|
|
||||||
// Upload all (must be waterfall so they get the right created_at)
|
|
||||||
const serverUrl = 'http://localhost:' + port;
|
|
||||||
for (let i = 0; i < chunks.length; i++) {
|
|
||||||
await fetch(serverUrl + '/classes/Ndl_CF', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
code: chunks[i],
|
|
||||||
version,
|
|
||||||
runtime,
|
|
||||||
ACL: {
|
|
||||||
"*": {
|
|
||||||
read: false,
|
|
||||||
write: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), // Make it only accessible to masterkey
|
|
||||||
headers: {
|
|
||||||
'X-Parse-Application-Id': appId,
|
|
||||||
'X-Parse-Master-Key': masterKey
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
deployFunctions,
|
|
||||||
getLatestVersion: getLatestVersionCached
|
|
||||||
};
|
|
||||||
97
packages/noodl-cloudservice/src/function-deploy.ts
Normal file
97
packages/noodl-cloudservice/src/function-deploy.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import fetch from 'node-fetch';
|
||||||
|
import { Utils } from './utils';
|
||||||
|
|
||||||
|
export type GetLatestVersionOptions = {
|
||||||
|
appId: string;
|
||||||
|
masterKey: string;
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CFVersion = {
|
||||||
|
functionVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest version of cloud functions deploy, if not provided in header
|
||||||
|
async function fetchLatestVersion({ appId, masterKey, port }: GetLatestVersionOptions): Promise<CFVersion | undefined> {
|
||||||
|
const res = await fetch('http://localhost:' + port + '/classes/Ndl_CF?limit=1&order=-createdAt&keys=version', {
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': appId,
|
||||||
|
'X-Parse-Master-Key': masterKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.results && json.results.length === 1) {
|
||||||
|
return {
|
||||||
|
functionVersion: json.results[0].version,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CFVersionCache = CFVersion & { ttl: number; }
|
||||||
|
let _latestVersionCache: CFVersionCache | undefined = undefined;
|
||||||
|
|
||||||
|
export async function getLatestVersion(options: GetLatestVersionOptions): Promise<CFVersion> {
|
||||||
|
if (_latestVersionCache && (_latestVersionCache.ttl === undefined || _latestVersionCache.ttl > Date.now())) {
|
||||||
|
return _latestVersionCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
_latestVersionCache = undefined;
|
||||||
|
|
||||||
|
const latestVersion = await fetchLatestVersion(options);
|
||||||
|
if (latestVersion) {
|
||||||
|
_latestVersionCache = {
|
||||||
|
...latestVersion,
|
||||||
|
ttl: Date.now() + 15 * 1000 // Cache for 15s
|
||||||
|
};
|
||||||
|
|
||||||
|
return _latestVersionCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deployFunctions({
|
||||||
|
port,
|
||||||
|
appId,
|
||||||
|
masterKey,
|
||||||
|
runtime,
|
||||||
|
data
|
||||||
|
}) {
|
||||||
|
const deploy = "const _exportedComponents = " + data
|
||||||
|
const version = Utils.randomString(16)
|
||||||
|
|
||||||
|
// Split deploy into 100kb sizes
|
||||||
|
const chunks = Utils.chunkString(deploy, 100 * 1024);
|
||||||
|
|
||||||
|
// Upload all (must be waterfall so they get the right created_at)
|
||||||
|
const serverUrl = 'http://localhost:' + port;
|
||||||
|
for (let i = 0; i < chunks.length; i++) {
|
||||||
|
await fetch(serverUrl + '/classes/Ndl_CF', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
code: chunks[i],
|
||||||
|
version,
|
||||||
|
runtime,
|
||||||
|
ACL: {
|
||||||
|
"*": {
|
||||||
|
read: false,
|
||||||
|
write: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), // Make it only accessible to masterkey
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': appId,
|
||||||
|
'X-Parse-Master-Key': masterKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
const CFContext = require('./cfcontext')
|
import { getCachedContext, scheduleContextCachePurge } from './cfcontext';
|
||||||
|
import { CFVersion } from './function-deploy';
|
||||||
|
import { Logger } from './logger';
|
||||||
|
|
||||||
// The logger that is needed by the cloud functions
|
// The logger that is needed by the cloud functions
|
||||||
// it passes the logs to the parse server logger
|
// it passes the logs to the parse server logger
|
||||||
class FunctionLogger {
|
export class FunctionLogger {
|
||||||
|
noodlParseServer: any;
|
||||||
|
|
||||||
constructor(noodlParseServer) {
|
constructor(noodlParseServer) {
|
||||||
this.noodlParseServer = noodlParseServer;
|
this.noodlParseServer = noodlParseServer;
|
||||||
}
|
}
|
||||||
@@ -14,7 +18,20 @@ class FunctionLogger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeFunction({
|
export type ExecuteFunctionOptions = {
|
||||||
|
port: number;
|
||||||
|
appId: string;
|
||||||
|
masterKey: string;
|
||||||
|
version: CFVersion;
|
||||||
|
logger: Logger;
|
||||||
|
headers: Record<string, unknown>
|
||||||
|
functionId: string;
|
||||||
|
body: string;
|
||||||
|
timeOut: number;
|
||||||
|
memoryLimit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function executeFunction({
|
||||||
port,
|
port,
|
||||||
appId,
|
appId,
|
||||||
masterKey,
|
masterKey,
|
||||||
@@ -25,30 +42,26 @@ async function executeFunction({
|
|||||||
body,
|
body,
|
||||||
timeOut = 15,
|
timeOut = 15,
|
||||||
memoryLimit = 256
|
memoryLimit = 256
|
||||||
}) {
|
}: ExecuteFunctionOptions) {
|
||||||
// Prepare the context
|
// Prepare the context
|
||||||
let cachedContext = await CFContext.getCachedContext({
|
let cachedContext = await getCachedContext({
|
||||||
backendEndpoint: 'http://localhost:' + port,
|
backendEndpoint: 'http://localhost:' + port,
|
||||||
appId,
|
appId,
|
||||||
masterKey,
|
masterKey,
|
||||||
version,
|
version,
|
||||||
logger,
|
logger,
|
||||||
timeOut: timeOut * 1000,
|
timeout: timeOut * 1000,
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
})
|
})
|
||||||
CFContext.scheduleContextCachePurge();
|
|
||||||
|
scheduleContextCachePurge();
|
||||||
|
|
||||||
// Execute the request
|
// Execute the request
|
||||||
const response = await cachedContext.handleRequest({
|
const response = await cachedContext.handleRequest({
|
||||||
functionId,
|
functionId,
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
})
|
});
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
FunctionLogger,
|
|
||||||
executeFunction
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
const { createNoodlParseServer } = require("./parse");
|
import { NoodlParseServerOptions, createNoodlParseServer } from "./parse";
|
||||||
const { executeFunction } = require("./function");
|
import { executeFunction } from "./function";
|
||||||
const { deployFunctions, getLatestVersionCached } = require("./function-deploy");
|
import { CFVersion, deployFunctions, getLatestVersion } from "./function-deploy";
|
||||||
const { Logger } = require("./logger");
|
import { Logger } from "./logger";
|
||||||
|
|
||||||
function createMiddleware(noodlServer) {
|
function createMiddleware(noodlServer) {
|
||||||
return async function middleware(req, res, next) {
|
return async function middleware(req, res, next) {
|
||||||
@@ -15,10 +15,10 @@ function createMiddleware(noodlServer) {
|
|||||||
|
|
||||||
console.log('Running cloud function ' + functionId);
|
console.log('Running cloud function ' + functionId);
|
||||||
|
|
||||||
let version = req.headers['x-noodl-cloud-version']
|
let requestVersion = req.headers['x-noodl-cloud-version'];
|
||||||
if (version === undefined) {
|
let version: CFVersion = requestVersion
|
||||||
version = await getLatestVersionCached(noodlServer.options)
|
? { functionVersion: requestVersion }
|
||||||
}
|
: await getLatestVersion(noodlServer.options)
|
||||||
|
|
||||||
// Execute the request
|
// Execute the request
|
||||||
const cfResponse = await executeFunction({
|
const cfResponse = await executeFunction({
|
||||||
@@ -98,24 +98,13 @@ function createMiddleware(noodlServer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function createNoodlServer(options: NoodlParseServerOptions) {
|
||||||
*
|
const noodlServer = createNoodlParseServer(options);
|
||||||
* @param {{
|
|
||||||
* port: number;
|
|
||||||
* databaseURI: string;
|
|
||||||
* masterKey: string;
|
|
||||||
* appId: string;
|
|
||||||
* functionOptions: { timeOut: number; memoryLimit: number; };
|
|
||||||
* parseOptions?: unknown;
|
|
||||||
* }} options
|
|
||||||
*/
|
|
||||||
function createNoodlServer(options) {
|
|
||||||
const noodlServer = createNoodlParseServer(options)
|
|
||||||
|
|
||||||
const cfMiddleware = createMiddleware(noodlServer);
|
const cfMiddleware = createMiddleware(noodlServer);
|
||||||
|
|
||||||
// Combine the Noodl Cloud Function middleware with the Parse middleware into one middleware.
|
// Combine the Noodl Cloud Function middleware with the Parse middleware into one middleware.
|
||||||
const middleware = (req, res, next) => {
|
const middleware = (req: Request, res: Response, next: () => void) => {
|
||||||
cfMiddleware(req, res, () => {
|
cfMiddleware(req, res, () => {
|
||||||
noodlServer.server.app(req, res, next);
|
noodlServer.server.app(req, res, next);
|
||||||
});
|
});
|
||||||
@@ -124,9 +113,5 @@ function createNoodlServer(options) {
|
|||||||
return {
|
return {
|
||||||
noodlServer,
|
noodlServer,
|
||||||
middleware
|
middleware
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createNoodlServer
|
|
||||||
};
|
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
// The logger that is needed by the cloud functions
|
// The logger that is needed by the cloud functions
|
||||||
// it passes the logs to the parse server logger
|
// it passes the logs to the parse server logger
|
||||||
class Logger {
|
export class Logger {
|
||||||
|
noodlServer: any;
|
||||||
|
|
||||||
constructor(noodlServer) {
|
constructor(noodlServer) {
|
||||||
this.noodlServer = noodlServer;
|
this.noodlServer = noodlServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
log(level, message) {
|
public log(level: string, message: string) {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
this.noodlServer.logger._log(level, message);
|
this.noodlServer.logger._log(level, message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
Logger,
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
const Winston = require('winston')
|
import Winston from 'winston';
|
||||||
require('winston-mongodb');
|
import { MongoDB } from 'winston-mongodb';
|
||||||
|
|
||||||
|
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
// This stuff is needed to get the mongo-db transport working
|
// This stuff is needed to get the mongo-db transport working
|
||||||
// https://github.com/winstonjs/winston/issues/1130
|
// https://github.com/winstonjs/winston/issues/1130
|
||||||
@@ -50,29 +52,33 @@ Transport.prototype.normalizeQuery = function (options) { //
|
|||||||
|
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
Transport.prototype.formatResults = function (results, options) {
|
Transport.prototype.formatResults = function (results, _options) {
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a logger that will push to mongodb
|
// Create a logger that will push to mongodb
|
||||||
class WinstonLoggerAdapter {
|
export class LoggerAdapter {
|
||||||
|
logger: Winston.Logger;
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
const info = new Winston.transports.MongoDB({
|
const info = new MongoDB({
|
||||||
db: options.databaseURI,
|
db: options.databaseURI,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
collection: '_ndl_logs_info',
|
collection: '_ndl_logs_info',
|
||||||
capped: true,
|
capped: true,
|
||||||
cappedSize: 2000000, // 2mb size
|
cappedSize: 2000000, // 2mb size
|
||||||
})
|
})
|
||||||
|
// @ts-expect-error
|
||||||
info.name = 'logs-info'
|
info.name = 'logs-info'
|
||||||
|
|
||||||
const error = new Winston.transports.MongoDB({
|
const error = new MongoDB({
|
||||||
db: options.databaseURI,
|
db: options.databaseURI,
|
||||||
level: 'error',
|
level: 'error',
|
||||||
collection: '_ndl_logs_error',
|
collection: '_ndl_logs_error',
|
||||||
capped: true,
|
capped: true,
|
||||||
cappedSize: 2000000, // 2mb size
|
cappedSize: 2000000, // 2mb size
|
||||||
})
|
})
|
||||||
|
// @ts-expect-error
|
||||||
error.name = 'logs-error'
|
error.name = 'logs-error'
|
||||||
|
|
||||||
this.logger = Winston.createLogger({
|
this.logger = Winston.createLogger({
|
||||||
@@ -96,7 +102,7 @@ class WinstonLoggerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// custom query as winston is currently limited
|
// custom query as winston is currently limited
|
||||||
query(options, callback = () => {}) {
|
query(options, callback = (_result) => {}) {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
@@ -108,12 +114,13 @@ class WinstonLoggerAdapter {
|
|||||||
const order = options.order || 'desc';
|
const order = options.order || 'desc';
|
||||||
const level = options.level || 'info';
|
const level = options.level || 'info';
|
||||||
|
|
||||||
const queryOptions = {
|
const queryOptions: Winston.QueryOptions = {
|
||||||
from,
|
from,
|
||||||
until,
|
until,
|
||||||
limit,
|
limit,
|
||||||
order,
|
order,
|
||||||
};
|
fields: {}
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.logger.query(queryOptions, (err, res) => {
|
this.logger.query(queryOptions, (err, res) => {
|
||||||
@@ -132,7 +139,3 @@ class WinstonLoggerAdapter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
LoggerAdapter: WinstonLoggerAdapter
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,39 @@
|
|||||||
const path = require('path');
|
import path from 'path';
|
||||||
const ParseServer = require('parse-server').default;
|
import ParseServer, { S3Adapter } from 'parse-server';
|
||||||
const {
|
import { LoggerAdapter } from './mongodb';
|
||||||
LoggerAdapter
|
|
||||||
} = require('./mongodb');
|
|
||||||
|
|
||||||
/**
|
export type NoodlParseServerOptions = {
|
||||||
*
|
port: number;
|
||||||
* @param {{
|
databaseURI: string;
|
||||||
* port: number;
|
masterKey: string;
|
||||||
* databaseURI: string;
|
appId: string;
|
||||||
* masterKey: string;
|
parseOptions?: Record<string, unknown>;
|
||||||
* appId: string;
|
|
||||||
* parseOptions?: unknown;
|
functionOptions: {
|
||||||
* }} param0
|
timeOut: number;
|
||||||
* @returns {{
|
memoryLimit: number;
|
||||||
* server: ParseServer;
|
}
|
||||||
* logger: LoggerAdapter;
|
}
|
||||||
* }}
|
|
||||||
*/
|
export type NoodlParseServerResult = {
|
||||||
function createNoodlParseServer({
|
functionOptions: NoodlParseServerOptions['functionOptions'];
|
||||||
|
options: {
|
||||||
|
port: number;
|
||||||
|
appId: string;
|
||||||
|
masterKey: string;
|
||||||
|
};
|
||||||
|
server: ParseServer;
|
||||||
|
logger: LoggerAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNoodlParseServer({
|
||||||
port = 3000,
|
port = 3000,
|
||||||
databaseURI,
|
databaseURI,
|
||||||
masterKey,
|
masterKey,
|
||||||
appId,
|
appId,
|
||||||
functionOptions,
|
functionOptions,
|
||||||
parseOptions = {},
|
parseOptions = {},
|
||||||
}) {
|
}: NoodlParseServerOptions): NoodlParseServerResult {
|
||||||
const serverURL = `http://localhost:${port}/`;
|
const serverURL = `http://localhost:${port}/`;
|
||||||
|
|
||||||
const logger = new LoggerAdapter({
|
const logger = new LoggerAdapter({
|
||||||
@@ -41,7 +49,6 @@ function createNoodlParseServer({
|
|||||||
throw Error("You must provide S3_SECRET_KEY and S3_ACCESS_KEY environment variables in addition to S3_BUCKET for S3 file storage.")
|
throw Error("You must provide S3_SECRET_KEY and S3_ACCESS_KEY environment variables in addition to S3_BUCKET for S3 file storage.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const S3Adapter = require('parse-server').S3Adapter;
|
|
||||||
filesAdapter = new S3Adapter(
|
filesAdapter = new S3Adapter(
|
||||||
process.env.S3_ACCESS_KEY,
|
process.env.S3_ACCESS_KEY,
|
||||||
process.env.S3_SECRET_KEY,
|
process.env.S3_SECRET_KEY,
|
||||||
@@ -73,7 +80,7 @@ function createNoodlParseServer({
|
|||||||
|
|
||||||
const server = new ParseServer({
|
const server = new ParseServer({
|
||||||
databaseURI,
|
databaseURI,
|
||||||
cloud: path.resolve(__dirname, './cloud.js'),
|
cloud: path.resolve(__dirname, './static/cloud.cjs'),
|
||||||
push: false,
|
push: false,
|
||||||
appId,
|
appId,
|
||||||
masterKey,
|
masterKey,
|
||||||
@@ -109,7 +116,3 @@ function createNoodlParseServer({
|
|||||||
logger,
|
logger,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createNoodlParseServer
|
|
||||||
}
|
|
||||||
25
packages/noodl-cloudservice/src/utils.ts
Normal file
25
packages/noodl-cloudservice/src/utils.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export namespace Utils {
|
||||||
|
export function randomString(size: number) {
|
||||||
|
if (size === 0) {
|
||||||
|
throw new Error("Zero-length randomString is useless.");
|
||||||
|
}
|
||||||
|
const chars =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789";
|
||||||
|
let objectId = "";
|
||||||
|
for (let i = 0; i < size; ++i) {
|
||||||
|
objectId += chars[Math.floor((1 + Math.random()) * 0x10000) % chars.length];
|
||||||
|
}
|
||||||
|
return objectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function chunkString(str: string, size: number) {
|
||||||
|
const numChunks = Math.ceil(str.length / size)
|
||||||
|
const chunks = new Array(numChunks)
|
||||||
|
|
||||||
|
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
|
||||||
|
chunks[i] = str.substr(o, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
}
|
||||||
20
packages/noodl-cloudservice/tsconfig.json
Normal file
20
packages/noodl-cloudservice/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"noEmit": false,
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ ANumber: 15
|
|||||||
ADate: 2022-11-07T10:23:52.301Z
|
ADate: 2022-11-07T10:23:52.301Z
|
||||||
AString: Test
|
AString: Test
|
||||||
ABoolean: true
|
ABoolean: true
|
||||||
AnObjetc: {"hej":"ho"}
|
AnObject: {"hej":"ho"}
|
||||||
AnArray: ["a", "b"]
|
AnArray: ["a", "b"]
|
||||||
Text: fetch-test
|
Text: fetch-test
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user