@budibase/server 0.9.185-alpha.2 → 0.9.185-alpha.20
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/Dockerfile +7 -1
- package/__mocks__/oracledb.ts +31 -0
- package/builder/assets/bb-space-man.5be51a45.svg +290 -0
- package/builder/assets/index.7d405e74.css +1 -0
- package/builder/assets/index.83e08172.js +1316 -0
- package/builder/assets/oracle.66592424.png +0 -0
- package/builder/assets/vendor.470042a4.js +152 -0
- package/builder/assets/vendor.5e1fe644.css +1 -0
- package/builder/index.html +4 -4
- package/coverage/clover.xml +785 -687
- package/coverage/coverage-final.json +90 -88
- package/coverage/lcov-report/api/controllers/analytics.js.html +2 -2
- package/coverage/lcov-report/api/controllers/apikeys.js.html +2 -2
- package/coverage/lcov-report/api/controllers/application.js.html +43 -13
- package/coverage/lcov-report/api/controllers/auth.js.html +7 -7
- package/coverage/lcov-report/api/controllers/automation.js.html +3 -3
- package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
- package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
- package/coverage/lcov-report/api/controllers/component.js.html +4 -4
- package/coverage/lcov-report/api/controllers/datasource.js.html +129 -21
- package/coverage/lcov-report/api/controllers/dev.js.html +16 -7
- package/coverage/lcov-report/api/controllers/hosting.js.html +10 -10
- package/coverage/lcov-report/api/controllers/index.html +77 -77
- package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
- package/coverage/lcov-report/api/controllers/layout.js.html +3 -3
- package/coverage/lcov-report/api/controllers/metadata.js.html +3 -3
- package/coverage/lcov-report/api/controllers/permission.js.html +3 -3
- package/coverage/lcov-report/api/controllers/query.js.html +48 -210
- package/coverage/lcov-report/api/controllers/role.js.html +9 -9
- package/coverage/lcov-report/api/controllers/routing.js.html +3 -3
- package/coverage/lcov-report/api/controllers/row/external.js.html +2 -2
- package/coverage/lcov-report/api/controllers/row/index.html +10 -10
- package/coverage/lcov-report/api/controllers/row/index.js.html +2 -2
- package/coverage/lcov-report/api/controllers/row/internal.js.html +9 -9
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +2 -2
- package/coverage/lcov-report/api/controllers/row/utils.js.html +2 -2
- package/coverage/lcov-report/api/controllers/screen.js.html +2 -2
- package/coverage/lcov-report/api/controllers/script.js.html +3 -3
- package/coverage/lcov-report/api/controllers/table/external.js.html +29 -8
- package/coverage/lcov-report/api/controllers/table/index.html +20 -20
- package/coverage/lcov-report/api/controllers/table/index.js.html +3 -3
- package/coverage/lcov-report/api/controllers/table/internal.js.html +2 -2
- package/coverage/lcov-report/api/controllers/table/utils.js.html +8 -8
- package/coverage/lcov-report/api/controllers/templates.js.html +4 -4
- package/coverage/lcov-report/api/controllers/user.js.html +2 -2
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.html +9 -9
- package/coverage/lcov-report/api/controllers/view/index.js.html +5 -5
- package/coverage/lcov-report/api/controllers/view/utils.js.html +11 -11
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +3 -3
- package/coverage/lcov-report/api/controllers/webhook.js.html +4 -4
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
- package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/routes/application.js.html +1 -1
- package/coverage/lcov-report/api/routes/auth.js.html +1 -1
- package/coverage/lcov-report/api/routes/automation.js.html +1 -1
- package/coverage/lcov-report/api/routes/backup.js.html +1 -1
- package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
- package/coverage/lcov-report/api/routes/component.js.html +1 -1
- package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
- package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
- package/coverage/lcov-report/api/routes/dev.js.html +2 -2
- package/coverage/lcov-report/api/routes/hosting.js.html +1 -1
- package/coverage/lcov-report/api/routes/index.html +4 -4
- package/coverage/lcov-report/api/routes/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/integration.js.html +1 -1
- package/coverage/lcov-report/api/routes/layout.js.html +1 -1
- package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
- package/coverage/lcov-report/api/routes/permission.js.html +1 -1
- package/coverage/lcov-report/api/routes/query.js.html +5 -2
- package/coverage/lcov-report/api/routes/role.js.html +1 -1
- package/coverage/lcov-report/api/routes/routing.js.html +1 -1
- package/coverage/lcov-report/api/routes/row.js.html +1 -1
- package/coverage/lcov-report/api/routes/screen.js.html +1 -1
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/static.js.html +2 -2
- package/coverage/lcov-report/api/routes/table.js.html +1 -1
- package/coverage/lcov-report/api/routes/templates.js.html +1 -1
- package/coverage/lcov-report/api/routes/user.js.html +1 -1
- package/coverage/lcov-report/api/routes/view.js.html +1 -1
- package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/actions.js.html +2 -2
- package/coverage/lcov-report/automations/automationUtils.js.html +3 -3
- package/coverage/lcov-report/automations/bullboard.js.html +71 -20
- package/coverage/lcov-report/automations/index.html +33 -48
- package/coverage/lcov-report/automations/index.js.html +2 -2
- package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
- package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
- package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/discord.js.html +3 -3
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeScript.js.html +3 -3
- package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
- package/coverage/lcov-report/automations/steps/index.html +14 -14
- package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +3 -3
- package/coverage/lcov-report/automations/steps/queryRows.js.html +3 -3
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
- package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
- package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
- package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/utils.js.html +7 -7
- package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/app.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/cron.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.js.html +8 -8
- package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +2 -2
- package/coverage/lcov-report/automations/triggers.js.html +22 -10
- package/coverage/lcov-report/automations/utils.js.html +42 -12
- package/coverage/lcov-report/constants/index.html +5 -5
- package/coverage/lcov-report/constants/index.js.html +44 -23
- package/coverage/lcov-report/constants/layouts.js.html +1 -1
- package/coverage/lcov-report/constants/screens.js.html +1 -1
- package/coverage/lcov-report/db/client.js.html +2 -2
- package/coverage/lcov-report/db/inMemoryView.js.html +2 -2
- package/coverage/lcov-report/db/index.html +2 -2
- package/coverage/lcov-report/db/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +6 -6
- package/coverage/lcov-report/db/linkedRows/index.html +10 -10
- package/coverage/lcov-report/db/linkedRows/index.js.html +5 -5
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +6 -6
- package/coverage/lcov-report/db/newid.js.html +3 -3
- package/coverage/lcov-report/db/utils.js.html +57 -57
- package/coverage/lcov-report/events/AutomationEmitter.js.html +6 -6
- package/coverage/lcov-report/events/index.html +6 -6
- package/coverage/lcov-report/events/index.js.html +1 -1
- package/coverage/lcov-report/events/utils.js.html +5 -5
- package/coverage/lcov-report/index.html +86 -71
- package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
- package/coverage/lcov-report/integrations/tests/index.html +1 -1
- package/coverage/lcov-report/middleware/appInfo.js.html +2 -2
- package/coverage/lcov-report/middleware/authorized.js.html +4 -4
- package/coverage/lcov-report/middleware/builder.js.html +16 -7
- package/coverage/lcov-report/middleware/currentapp.js.html +6 -6
- package/coverage/lcov-report/middleware/index.html +21 -21
- package/coverage/lcov-report/middleware/joi-validator.js.html +2 -2
- package/coverage/lcov-report/middleware/resourceId.js.html +18 -18
- package/coverage/lcov-report/middleware/selfhost.js.html +2 -2
- package/coverage/lcov-report/middleware/usageQuota.js.html +4 -4
- package/coverage/lcov-report/middleware/utils.js.html +1 -1
- package/coverage/lcov-report/{automations/thread.js.html → threads/automation.js.html} +90 -33
- package/coverage/lcov-report/threads/index.html +141 -0
- package/coverage/lcov-report/threads/index.js.html +278 -0
- package/coverage/lcov-report/threads/query.js.html +278 -0
- package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
- package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
- package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
- package/coverage/lcov-report/utilities/csvParser.js.html +2 -2
- package/coverage/lcov-report/utilities/global.js.html +2 -2
- package/coverage/lcov-report/utilities/index.html +21 -21
- package/coverage/lcov-report/utilities/index.js.html +12 -9
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +30 -9
- package/coverage/lcov-report/utilities/queue/index.html +14 -14
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.html +4 -4
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +2 -2
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +3 -3
- package/coverage/lcov-report/utilities/scriptRunner.js.html +16 -7
- package/coverage/lcov-report/utilities/security.js.html +3 -3
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/users.js.html +1 -1
- package/coverage/lcov-report/utilities/workerRequests.js.html +5 -5
- package/coverage/lcov.info +1247 -1070
- package/dist/api/controllers/application.js +11 -1
- package/dist/api/controllers/datasource.js +31 -7
- package/dist/api/controllers/deploy/index.js +2 -0
- package/dist/api/controllers/dev.js +4 -1
- package/dist/api/controllers/query.js +27 -57
- package/dist/api/controllers/table/external.js +8 -1
- package/dist/api/routes/query.js +1 -1
- package/dist/app.js +2 -11
- package/dist/automations/bullboard.js +37 -10
- package/dist/automations/triggers.js +7 -3
- package/dist/automations/utils.js +11 -2
- package/dist/constants/index.js +6 -0
- package/dist/definitions/datasource.js +1 -0
- package/dist/environment.js +13 -0
- package/dist/integrations/base/IntegrationBase.js +2 -0
- package/dist/integrations/base/sql.js +40 -8
- package/dist/integrations/index.js +6 -0
- package/dist/integrations/mysql.js +3 -1
- package/dist/integrations/oracle.js +345 -0
- package/dist/integrations/utils.js +17 -2
- package/dist/middleware/builder.js +4 -1
- package/dist/package.json +11 -6
- package/dist/{automations/thread.js → threads/automation.js} +26 -8
- package/dist/threads/index.js +64 -0
- package/dist/threads/query.js +64 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utilities/index.js +1 -0
- package/dist/utilities/queue/inMemoryQueue.js +8 -0
- package/dist/utilities/scriptRunner.js +4 -1
- package/package.json +12 -7
- package/scripts/dev/manage.js +1 -0
- package/scripts/integrations/oracle/docker-compose.yml +19 -0
- package/scripts/integrations/oracle/instantclient/linux/x86-64/basiclite-21.4.zip +0 -0
- package/scripts/integrations/oracle/instantclient/linux/x86-64/install.sh +23 -0
- package/scripts/integrations/oracle/oracle.md +90 -0
- package/scripts/integrations/postgres/docker-compose.yml +3 -3
- package/scripts/likeCypress.ts +33 -0
- package/src/api/controllers/application.js +11 -1
- package/src/api/controllers/datasource.js +45 -9
- package/src/api/controllers/deploy/index.js +2 -0
- package/src/api/controllers/dev.js +4 -1
- package/src/api/controllers/query.js +26 -80
- package/src/api/controllers/table/external.js +8 -1
- package/src/api/routes/query.js +1 -0
- package/src/app.ts +2 -10
- package/src/automations/bullboard.js +28 -11
- package/src/automations/tests/automation.spec.js +3 -4
- package/src/automations/triggers.js +7 -3
- package/src/automations/utils.js +12 -2
- package/src/constants/index.js +7 -0
- package/src/definitions/datasource.ts +1 -0
- package/src/environment.js +15 -0
- package/src/integrations/airtable.ts +2 -1
- package/src/integrations/arangodb.ts +2 -1
- package/src/integrations/base/IntegrationBase.ts +6 -0
- package/src/integrations/base/datasourcePlus.ts +2 -1
- package/src/integrations/base/sql.ts +41 -9
- package/src/integrations/couchdb.ts +2 -1
- package/src/integrations/dynamodb.ts +2 -1
- package/src/integrations/elasticsearch.ts +2 -1
- package/src/integrations/index.ts +7 -0
- package/src/integrations/mongodb.ts +2 -1
- package/src/integrations/mysql.ts +1 -1
- package/src/integrations/oracle.ts +445 -0
- package/src/integrations/rest.ts +2 -1
- package/src/integrations/s3.ts +2 -1
- package/src/integrations/tests/oracle.spec.js +94 -0
- package/src/integrations/utils.ts +16 -2
- package/src/middleware/builder.js +4 -1
- package/src/{automations/thread.js → threads/automation.js} +27 -8
- package/src/threads/index.js +66 -0
- package/src/threads/query.js +66 -0
- package/src/utilities/index.js +1 -0
- package/src/utilities/queue/inMemoryQueue.js +7 -0
- package/src/utilities/scriptRunner.js +4 -1
- package/tsconfig.json +1 -1
- package/builder/assets/index.0eadd2f5.js +0 -201
- package/builder/assets/index.a79ec0ef.css +0 -29
- package/builder/assets/vendor.278a305c.js +0 -9
- package/builder/assets/vendor.d75d5eb7.css +0 -1
package/dist/utilities/index.js
CHANGED
|
@@ -52,6 +52,7 @@ exports.objectStoreUrl = () => {
|
|
|
52
52
|
* via a specific endpoint (under /api/assets/client).
|
|
53
53
|
* @param {string} appId In production we need the appId to look up the correct bucket, as the
|
|
54
54
|
* version of the client lib may differ between apps.
|
|
55
|
+
* @param {string} version The version to retrieve.
|
|
55
56
|
* @return {string} The URL to be inserted into appPackage response or server rendered
|
|
56
57
|
* app index file.
|
|
57
58
|
*/
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const fetch = require("node-fetch");
|
|
3
3
|
const { VM, VMScript } = require("vm2");
|
|
4
|
+
const JS_TIMEOUT_MS = 1000;
|
|
4
5
|
class ScriptRunner {
|
|
5
6
|
constructor(script, context) {
|
|
6
7
|
const code = `let fn = () => {\n${script}\n}; results.out = fn();`;
|
|
7
|
-
this.vm = new VM(
|
|
8
|
+
this.vm = new VM({
|
|
9
|
+
timeout: JS_TIMEOUT_MS,
|
|
10
|
+
});
|
|
8
11
|
this.results = { out: "" };
|
|
9
12
|
this.vm.setGlobals(context);
|
|
10
13
|
this.vm.setGlobal("fetch", fetch);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "0.9.185-alpha.
|
|
4
|
+
"version": "0.9.185-alpha.20",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"repository": {
|
|
@@ -68,9 +68,11 @@
|
|
|
68
68
|
"author": "Budibase",
|
|
69
69
|
"license": "AGPL-3.0-or-later",
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@budibase/auth": "^0.9.185-alpha.
|
|
72
|
-
"@budibase/client": "^0.9.185-alpha.
|
|
73
|
-
"@budibase/string-templates": "^0.9.185-alpha.
|
|
71
|
+
"@budibase/auth": "^0.9.185-alpha.20",
|
|
72
|
+
"@budibase/client": "^0.9.185-alpha.20",
|
|
73
|
+
"@budibase/string-templates": "^0.9.185-alpha.20",
|
|
74
|
+
"@bull-board/api": "^3.7.0",
|
|
75
|
+
"@bull-board/koa": "^3.7.0",
|
|
74
76
|
"@elastic/elasticsearch": "7.10.0",
|
|
75
77
|
"@koa/router": "8.0.0",
|
|
76
78
|
"@sendgrid/mail": "7.1.1",
|
|
@@ -80,7 +82,6 @@
|
|
|
80
82
|
"aws-sdk": "^2.767.0",
|
|
81
83
|
"bcryptjs": "2.4.3",
|
|
82
84
|
"bull": "^3.22.4",
|
|
83
|
-
"bull-board": "^2.0.1",
|
|
84
85
|
"chmodr": "1.2.0",
|
|
85
86
|
"csvtojson": "2.0.10",
|
|
86
87
|
"dotenv": "8.2.0",
|
|
@@ -120,6 +121,7 @@
|
|
|
120
121
|
"uuid": "3.3.2",
|
|
121
122
|
"validate.js": "0.13.1",
|
|
122
123
|
"vm2": "^3.9.3",
|
|
124
|
+
"worker-farm": "^1.7.0",
|
|
123
125
|
"yargs": "13.2.4",
|
|
124
126
|
"zlib": "1.0.5"
|
|
125
127
|
},
|
|
@@ -133,12 +135,12 @@
|
|
|
133
135
|
"@types/koa": "^2.13.3",
|
|
134
136
|
"@types/koa-router": "^7.4.2",
|
|
135
137
|
"@types/node": "^15.12.4",
|
|
138
|
+
"@types/oracledb": "^5.2.1",
|
|
136
139
|
"@typescript-eslint/parser": "4.28.0",
|
|
137
140
|
"babel-jest": "^27.0.2",
|
|
138
141
|
"copyfiles": "^2.4.1",
|
|
139
142
|
"docker-compose": "^0.23.6",
|
|
140
143
|
"eslint": "^6.8.0",
|
|
141
|
-
"express": "^4.17.1",
|
|
142
144
|
"jest": "^27.0.5",
|
|
143
145
|
"nodemon": "^2.0.4",
|
|
144
146
|
"prettier": "^2.3.1",
|
|
@@ -149,5 +151,8 @@
|
|
|
149
151
|
"typescript": "^4.3.5",
|
|
150
152
|
"update-dotenv": "^1.1.1"
|
|
151
153
|
},
|
|
152
|
-
"
|
|
154
|
+
"optionalDependencies": {
|
|
155
|
+
"oracledb": "^5.3.0"
|
|
156
|
+
},
|
|
157
|
+
"gitHead": "5f40c6e234ea415c93ced4a2d13a0c2194be94a2"
|
|
153
158
|
}
|
package/scripts/dev/manage.js
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# For more information see:
|
|
2
|
+
# https://container-registry.oracle.com/
|
|
3
|
+
# - Database > Express
|
|
4
|
+
version: "3.8"
|
|
5
|
+
services:
|
|
6
|
+
db:
|
|
7
|
+
container_name: oracle-xe
|
|
8
|
+
platform: linux/x86_64
|
|
9
|
+
image: container-registry.oracle.com/database/express:18.4.0-xe
|
|
10
|
+
environment:
|
|
11
|
+
ORACLE_PWD: oracle
|
|
12
|
+
ports:
|
|
13
|
+
- 1521:1521
|
|
14
|
+
- 5500:5500
|
|
15
|
+
volumes:
|
|
16
|
+
- oracle_data:/opt/oracle/oradata
|
|
17
|
+
|
|
18
|
+
volumes:
|
|
19
|
+
oracle_data:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Must be root to continue
|
|
4
|
+
if [[ $(id -u) -ne 0 ]] ; then echo "Please run as root" ; exit 1 ; fi
|
|
5
|
+
|
|
6
|
+
# Allow for re-runs
|
|
7
|
+
rm -rf /opt/oracle
|
|
8
|
+
|
|
9
|
+
echo "Installing oracle instant client"
|
|
10
|
+
|
|
11
|
+
# copy and unzip package
|
|
12
|
+
mkdir -p /opt/oracle
|
|
13
|
+
cp scripts/integrations/oracle/instantclient/linux/x86-64/basiclite-21.4.zip /opt/oracle
|
|
14
|
+
cd /opt/oracle
|
|
15
|
+
unzip -qq basiclite-21.4.zip -d .
|
|
16
|
+
rm *.zip
|
|
17
|
+
mv instantclient* instantclient
|
|
18
|
+
|
|
19
|
+
# update runtime link path
|
|
20
|
+
sh -c "echo /opt/oracle/instantclient > /etc/ld.so.conf.d/oracle-instantclient.conf"
|
|
21
|
+
ldconfig /etc/ld.so.conf.d
|
|
22
|
+
|
|
23
|
+
echo "Installation complete"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
## Database
|
|
4
|
+
|
|
5
|
+
**Important**
|
|
6
|
+
- Oracle database is supported only on **x86-64 architecture**
|
|
7
|
+
- Oracle database is **not supported on Mac ARM architecture** (either via docker or linux virtualization)
|
|
8
|
+
|
|
9
|
+
To install oracle express edition simply run `docker-compose up`
|
|
10
|
+
|
|
11
|
+
- A single instance pluggable database (PDB) will be created named `xepdb`
|
|
12
|
+
- The default password is configured in the compose file as `oracle`
|
|
13
|
+
- The `system`, `sys` and `pdbadmin` users all share this password
|
|
14
|
+
|
|
15
|
+
## Instant Client
|
|
16
|
+
|
|
17
|
+
Before oracle can be connected to from nodejs, the oracle client must be installed.
|
|
18
|
+
For more information see https://www.oracle.com/database/technologies/instant-client/downloads.html
|
|
19
|
+
|
|
20
|
+
**Important**
|
|
21
|
+
- Oracle client is supported only on **x86-64 architecture**
|
|
22
|
+
- Oracle client is **not supported on Mac ARM architecture**
|
|
23
|
+
|
|
24
|
+
### Linux
|
|
25
|
+
Run the provided install script for linux from the `server` root path:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
sudo /bin/bash -e scripts/integrations/oracle/instantclient/linux/x86-64/install.sh
|
|
29
|
+
```
|
|
30
|
+
For more information see: https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html#ic_x64_inst
|
|
31
|
+
|
|
32
|
+
### Mac
|
|
33
|
+
**This has not yet been tested**
|
|
34
|
+
|
|
35
|
+
See: https://www.oracle.com/database/technologies/instant-client/macos-intel-x86-downloads.html#ic_osx_inst
|
|
36
|
+
|
|
37
|
+
# Management
|
|
38
|
+
To connect to oracle sql command line:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
docker exec -it oracle-xe sqlplus -l system/oracle@localhost/xepdb1
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
To create a new schema (where a user is the same as a schema in oracle) named `sales`:
|
|
45
|
+
|
|
46
|
+
```sql
|
|
47
|
+
define USERNAME = sales
|
|
48
|
+
|
|
49
|
+
create user &USERNAME;
|
|
50
|
+
|
|
51
|
+
alter user &USERNAME
|
|
52
|
+
default tablespace users
|
|
53
|
+
temporary tablespace temp
|
|
54
|
+
quota unlimited on users;
|
|
55
|
+
|
|
56
|
+
grant create session,
|
|
57
|
+
create view,
|
|
58
|
+
create sequence,
|
|
59
|
+
create procedure,
|
|
60
|
+
create table,
|
|
61
|
+
create trigger,
|
|
62
|
+
create type,
|
|
63
|
+
create materialized view
|
|
64
|
+
to &USERNAME;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
To set the password for the sales schema use:
|
|
68
|
+
|
|
69
|
+
```sql
|
|
70
|
+
define USERNAME = sales
|
|
71
|
+
define PASSWORD = sales
|
|
72
|
+
|
|
73
|
+
alter user &USERNAME identified by &PASSWORD;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
As before the database schema can now be connected to using:
|
|
77
|
+
```bash
|
|
78
|
+
docker exec -it oracle-xe sqlplus -l sales/sales@localhost:1521/xepdb1
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## HR Schema
|
|
82
|
+
|
|
83
|
+
The `HR` schema is populated with dummy data by default in oracle for testing purposes.
|
|
84
|
+
To connect to the HR schema first update the user password and unlock the account by performing
|
|
85
|
+
```sql
|
|
86
|
+
ALTER USER hr ACCOUNT UNLOCK;
|
|
87
|
+
ALTER USER hr IDENTIFIED BY hr
|
|
88
|
+
```
|
|
89
|
+
You should now be able to connect to the hr schema using the credentials hr/hr
|
|
90
|
+
|
|
@@ -11,7 +11,7 @@ services:
|
|
|
11
11
|
ports:
|
|
12
12
|
- "5432:5432"
|
|
13
13
|
volumes:
|
|
14
|
-
|
|
14
|
+
- pg_data:/var/lib/postgresql/data/
|
|
15
15
|
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
16
16
|
|
|
17
17
|
pgadmin:
|
|
@@ -24,5 +24,5 @@ services:
|
|
|
24
24
|
ports:
|
|
25
25
|
- "5050:80"
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
volumes:
|
|
28
|
+
pg_data:
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/******************************************************
|
|
2
|
+
* This script just makes it easy to re-create *
|
|
3
|
+
* a cypress like environment for testing the backend *
|
|
4
|
+
******************************************************/
|
|
5
|
+
const path = require("path")
|
|
6
|
+
const tmpdir = path.join(require("os").tmpdir(), ".budibase")
|
|
7
|
+
|
|
8
|
+
const MAIN_PORT = "10001"
|
|
9
|
+
const WORKER_PORT = "10002"
|
|
10
|
+
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
process.env.PORT = MAIN_PORT
|
|
13
|
+
process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE"
|
|
14
|
+
process.env.NODE_ENV = "cypress"
|
|
15
|
+
process.env.ENABLE_ANALYTICS = "false"
|
|
16
|
+
process.env.JWT_SECRET = "budibase"
|
|
17
|
+
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
|
|
18
|
+
process.env.SELF_HOSTED = "1"
|
|
19
|
+
process.env.WORKER_URL = `http://localhost:${WORKER_PORT}/`
|
|
20
|
+
process.env.MINIO_URL = `http://localhost:${MAIN_PORT}/`
|
|
21
|
+
process.env.MINIO_ACCESS_KEY = "budibase"
|
|
22
|
+
process.env.MINIO_SECRET_KEY = "budibase"
|
|
23
|
+
process.env.COUCH_DB_USER = "budibase"
|
|
24
|
+
process.env.COUCH_DB_PASSWORD = "budibase"
|
|
25
|
+
process.env.INTERNAL_API_KEY = "budibase"
|
|
26
|
+
process.env.ALLOW_DEV_AUTOMATIONS = "1"
|
|
27
|
+
|
|
28
|
+
// don't make this a variable or top level require
|
|
29
|
+
// it will cause environment module to be loaded prematurely
|
|
30
|
+
const server = require("../src/app")
|
|
31
|
+
process.env.PORT = WORKER_PORT
|
|
32
|
+
const worker = require("../../worker/src/index")
|
|
33
|
+
process.env.PORT = MAIN_PORT
|
|
@@ -45,6 +45,8 @@ const {
|
|
|
45
45
|
} = require("../../utilities/fileSystem/clientLibrary")
|
|
46
46
|
const { getTenantId, isMultiTenant } = require("@budibase/auth/tenancy")
|
|
47
47
|
const { syncGlobalUsers } = require("./user")
|
|
48
|
+
const { app: appCache } = require("@budibase/auth/cache")
|
|
49
|
+
const { cleanupAutomations } = require("../../automations/utils")
|
|
48
50
|
|
|
49
51
|
const URL_REGEX_SLASH = /\/|\\/g
|
|
50
52
|
|
|
@@ -254,6 +256,7 @@ exports.create = async ctx => {
|
|
|
254
256
|
await createApp(appId)
|
|
255
257
|
}
|
|
256
258
|
|
|
259
|
+
await appCache.invalidateAppMetadata(appId, newApplication)
|
|
257
260
|
ctx.status = 200
|
|
258
261
|
ctx.body = newApplication
|
|
259
262
|
}
|
|
@@ -317,8 +320,12 @@ exports.delete = async ctx => {
|
|
|
317
320
|
if (!env.isTest() && !ctx.query.unpublish) {
|
|
318
321
|
await deleteApp(ctx.params.appId)
|
|
319
322
|
}
|
|
323
|
+
if (ctx.query && ctx.query.unpublish) {
|
|
324
|
+
await cleanupAutomations(ctx.params.appId)
|
|
325
|
+
}
|
|
320
326
|
// make sure the app/role doesn't stick around after the app has been deleted
|
|
321
327
|
await removeAppFromUserRoles(ctx, ctx.params.appId)
|
|
328
|
+
await appCache.invalidateAppMetadata(ctx.params.appId)
|
|
322
329
|
|
|
323
330
|
ctx.status = 200
|
|
324
331
|
ctx.body = result
|
|
@@ -387,7 +394,10 @@ const updateAppPackage = async (ctx, appPackage, appId) => {
|
|
|
387
394
|
// Redis, shouldn't ever store it
|
|
388
395
|
delete newAppPackage.lockedBy
|
|
389
396
|
|
|
390
|
-
|
|
397
|
+
const response = await db.put(newAppPackage)
|
|
398
|
+
// remove any cached metadata, so that it will be updated
|
|
399
|
+
await appCache.invalidateAppMetadata(appId)
|
|
400
|
+
return response
|
|
391
401
|
}
|
|
392
402
|
|
|
393
403
|
const createEmptyAppPackage = async (ctx, app) => {
|
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
BudibaseInternalDB,
|
|
8
8
|
getTableParams,
|
|
9
9
|
} = require("../../db/utils")
|
|
10
|
-
const { BuildSchemaErrors } = require("../../constants")
|
|
10
|
+
const { BuildSchemaErrors, InvalidColumns } = require("../../constants")
|
|
11
11
|
const { integrations } = require("../../integrations")
|
|
12
12
|
const { getDatasourceAndQuery } = require("./row/utils")
|
|
13
13
|
|
|
@@ -119,8 +119,16 @@ exports.destroy = async function (ctx) {
|
|
|
119
119
|
const db = new CouchDB(ctx.appId)
|
|
120
120
|
|
|
121
121
|
// Delete all queries for the datasource
|
|
122
|
-
const
|
|
123
|
-
|
|
122
|
+
const queries = await db.allDocs(
|
|
123
|
+
getQueryParams(ctx.params.datasourceId, null)
|
|
124
|
+
)
|
|
125
|
+
await db.bulkDocs(
|
|
126
|
+
queries.rows.map(row => ({
|
|
127
|
+
_id: row.id,
|
|
128
|
+
_rev: row.value.rev,
|
|
129
|
+
_deleted: true,
|
|
130
|
+
}))
|
|
131
|
+
)
|
|
124
132
|
|
|
125
133
|
// delete the datasource
|
|
126
134
|
await db.remove(ctx.params.datasourceId, ctx.params.revId)
|
|
@@ -144,6 +152,23 @@ exports.query = async function (ctx) {
|
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
154
|
|
|
155
|
+
function getErrorTables(errors, errorType) {
|
|
156
|
+
return Object.entries(errors)
|
|
157
|
+
.filter(entry => entry[1] === errorType)
|
|
158
|
+
.map(([name]) => name)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function updateError(error, newError, tables) {
|
|
162
|
+
if (!error) {
|
|
163
|
+
error = ""
|
|
164
|
+
}
|
|
165
|
+
if (error.length > 0) {
|
|
166
|
+
error += "\n"
|
|
167
|
+
}
|
|
168
|
+
error += `${newError} ${tables.join(", ")}`
|
|
169
|
+
return error
|
|
170
|
+
}
|
|
171
|
+
|
|
147
172
|
const buildSchemaHelper = async datasource => {
|
|
148
173
|
const Connector = integrations[datasource.source]
|
|
149
174
|
|
|
@@ -168,12 +193,23 @@ const buildSchemaHelper = async datasource => {
|
|
|
168
193
|
const errors = connector.schemaErrors
|
|
169
194
|
let error = null
|
|
170
195
|
if (errors && Object.keys(errors).length > 0) {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
196
|
+
const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
|
|
197
|
+
const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
|
|
198
|
+
if (noKey.length) {
|
|
199
|
+
error = updateError(
|
|
200
|
+
error,
|
|
201
|
+
"No primary key constraint found for the following:",
|
|
202
|
+
noKey
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
if (invalidCol.length) {
|
|
206
|
+
const invalidCols = Object.values(InvalidColumns).join(", ")
|
|
207
|
+
error = updateError(
|
|
208
|
+
error,
|
|
209
|
+
`Cannot use columns ${invalidCols} found in following:`,
|
|
210
|
+
invalidCol
|
|
211
|
+
)
|
|
212
|
+
}
|
|
177
213
|
}
|
|
178
214
|
return { tables: connector.tables, error }
|
|
179
215
|
}
|
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
disableAllCrons,
|
|
7
7
|
enableCronTrigger,
|
|
8
8
|
} = require("../../../automations/utils")
|
|
9
|
+
const { app: appCache } = require("@budibase/auth/cache")
|
|
9
10
|
|
|
10
11
|
// the max time we can wait for an invalidation to complete before considering it failed
|
|
11
12
|
const MAX_PENDING_TIME_MS = 30 * 60000
|
|
@@ -103,6 +104,7 @@ async function deployApp(deployment) {
|
|
|
103
104
|
appDoc.appId = productionAppId
|
|
104
105
|
appDoc.instance._id = productionAppId
|
|
105
106
|
await db.put(appDoc)
|
|
107
|
+
await appCache.invalidateAppMetadata(productionAppId)
|
|
106
108
|
console.log("New app doc written successfully.")
|
|
107
109
|
await initDeployedApp(productionAppId)
|
|
108
110
|
console.log("Deployed app initialised, setting deployment to successful")
|
|
@@ -6,6 +6,7 @@ const { request } = require("../../utilities/workerRequests")
|
|
|
6
6
|
const { clearLock } = require("../../utilities/redis")
|
|
7
7
|
const { Replication } = require("@budibase/auth").db
|
|
8
8
|
const { DocumentTypes } = require("../../db/utils")
|
|
9
|
+
const { app: appCache } = require("@budibase/auth/cache")
|
|
9
10
|
|
|
10
11
|
async function redirect(ctx, method, path = "global") {
|
|
11
12
|
const { devPath } = ctx.params
|
|
@@ -24,7 +25,8 @@ async function redirect(ctx, method, path = "global") {
|
|
|
24
25
|
)
|
|
25
26
|
)
|
|
26
27
|
if (response.status !== 200) {
|
|
27
|
-
|
|
28
|
+
const err = await response.text()
|
|
29
|
+
ctx.throw(400, err)
|
|
28
30
|
}
|
|
29
31
|
const cookie = response.headers.get("set-cookie")
|
|
30
32
|
if (cookie) {
|
|
@@ -106,6 +108,7 @@ exports.revert = async ctx => {
|
|
|
106
108
|
appDoc.appId = appId
|
|
107
109
|
appDoc.instance._id = appId
|
|
108
110
|
await db.put(appDoc)
|
|
111
|
+
await appCache.invalidateAppMetadata(appId)
|
|
109
112
|
ctx.body = {
|
|
110
113
|
message: "Reverted changes successfully.",
|
|
111
114
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const { processString } = require("@budibase/string-templates")
|
|
2
2
|
const CouchDB = require("../../db")
|
|
3
3
|
const { generateQueryID, getQueryParams } = require("../../db/utils")
|
|
4
|
-
const { integrations } = require("../../integrations")
|
|
5
4
|
const { BaseQueryVerbs } = require("../../constants")
|
|
6
5
|
const env = require("../../environment")
|
|
7
|
-
const
|
|
6
|
+
const { Thread, ThreadType } = require("../../threads")
|
|
7
|
+
|
|
8
|
+
const Runner = new Thread(ThreadType.QUERY, { timeoutMs: 10000 })
|
|
8
9
|
|
|
9
10
|
// simple function to append "readable" to all read queries
|
|
10
11
|
function enrichQueries(input) {
|
|
@@ -18,47 +19,6 @@ function enrichQueries(input) {
|
|
|
18
19
|
return wasArray ? queries : queries[0]
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
function formatResponse(resp) {
|
|
22
|
-
if (typeof resp === "string") {
|
|
23
|
-
try {
|
|
24
|
-
resp = JSON.parse(resp)
|
|
25
|
-
} catch (err) {
|
|
26
|
-
resp = { response: resp }
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return resp
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function runAndTransform(
|
|
33
|
-
integration,
|
|
34
|
-
queryVerb,
|
|
35
|
-
enrichedQuery,
|
|
36
|
-
transformer
|
|
37
|
-
) {
|
|
38
|
-
let rows = formatResponse(await integration[queryVerb](enrichedQuery))
|
|
39
|
-
|
|
40
|
-
// transform as required
|
|
41
|
-
if (transformer) {
|
|
42
|
-
const runner = new ScriptRunner(transformer, { data: rows })
|
|
43
|
-
rows = runner.execute()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// needs to an array for next step
|
|
47
|
-
if (!Array.isArray(rows)) {
|
|
48
|
-
rows = [rows]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// map into JSON if just raw primitive here
|
|
52
|
-
if (rows.find(row => typeof row !== "object")) {
|
|
53
|
-
rows = rows.map(value => ({ value }))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// get all the potential fields in the schema
|
|
57
|
-
let keys = rows.flatMap(Object.keys)
|
|
58
|
-
|
|
59
|
-
return { rows, keys }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
22
|
exports.fetch = async function (ctx) {
|
|
63
23
|
const db = new CouchDB(ctx.appId)
|
|
64
24
|
|
|
@@ -143,30 +103,23 @@ exports.preview = async function (ctx) {
|
|
|
143
103
|
|
|
144
104
|
const datasource = await db.get(ctx.request.body.datasourceId)
|
|
145
105
|
|
|
146
|
-
const Integration = integrations[datasource.source]
|
|
147
|
-
|
|
148
|
-
if (!Integration) {
|
|
149
|
-
ctx.throw(400, "Integration type does not exist.")
|
|
150
|
-
}
|
|
151
|
-
|
|
152
106
|
const { fields, parameters, queryVerb, transformer } = ctx.request.body
|
|
153
107
|
const enrichedQuery = await enrichQueryFields(fields, parameters)
|
|
154
|
-
const integration = new Integration(datasource.config)
|
|
155
108
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
109
|
+
try {
|
|
110
|
+
const { rows, keys } = await Runner.run({
|
|
111
|
+
datasource,
|
|
112
|
+
queryVerb,
|
|
113
|
+
query: enrichedQuery,
|
|
114
|
+
transformer,
|
|
115
|
+
})
|
|
162
116
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
integration.end()
|
|
117
|
+
ctx.body = {
|
|
118
|
+
rows,
|
|
119
|
+
schemaFields: [...new Set(keys)],
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
ctx.throw(400, err)
|
|
170
123
|
}
|
|
171
124
|
}
|
|
172
125
|
|
|
@@ -176,29 +129,22 @@ exports.execute = async function (ctx) {
|
|
|
176
129
|
const query = await db.get(ctx.params.queryId)
|
|
177
130
|
const datasource = await db.get(query.datasourceId)
|
|
178
131
|
|
|
179
|
-
const Integration = integrations[datasource.source]
|
|
180
|
-
|
|
181
|
-
if (!Integration) {
|
|
182
|
-
ctx.throw(400, "Integration type does not exist.")
|
|
183
|
-
}
|
|
184
|
-
|
|
185
132
|
const enrichedQuery = await enrichQueryFields(
|
|
186
133
|
query.fields,
|
|
187
134
|
ctx.request.body.parameters
|
|
188
135
|
)
|
|
189
|
-
const integration = new Integration(datasource.config)
|
|
190
136
|
|
|
191
137
|
// call the relevant CRUD method on the integration class
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
138
|
+
try {
|
|
139
|
+
const { rows } = await Runner.run({
|
|
140
|
+
datasource,
|
|
141
|
+
queryVerb: query.queryVerb,
|
|
142
|
+
query: enrichedQuery,
|
|
143
|
+
transformer: query.transformer,
|
|
144
|
+
})
|
|
145
|
+
ctx.body = rows
|
|
146
|
+
} catch (err) {
|
|
147
|
+
ctx.throw(400, err)
|
|
202
148
|
}
|
|
203
149
|
}
|
|
204
150
|
|
|
@@ -161,9 +161,13 @@ function isRelationshipSetup(column) {
|
|
|
161
161
|
exports.save = async function (ctx) {
|
|
162
162
|
const appId = ctx.appId
|
|
163
163
|
const table = ctx.request.body
|
|
164
|
-
// can't do this
|
|
164
|
+
// can't do this right now
|
|
165
165
|
delete table.dataImport
|
|
166
166
|
const datasourceId = getDatasourceId(ctx.request.body)
|
|
167
|
+
// table doesn't exist already, note that it is created
|
|
168
|
+
if (!table._id) {
|
|
169
|
+
table.created = true
|
|
170
|
+
}
|
|
167
171
|
let tableToSave = {
|
|
168
172
|
type: "table",
|
|
169
173
|
_id: buildExternalTableId(datasourceId, table.name),
|
|
@@ -265,6 +269,9 @@ exports.save = async function (ctx) {
|
|
|
265
269
|
exports.destroy = async function (ctx) {
|
|
266
270
|
const appId = ctx.appId
|
|
267
271
|
const tableToDelete = await getTable(appId, ctx.params.tableId)
|
|
272
|
+
if (!tableToDelete || !tableToDelete.created) {
|
|
273
|
+
ctx.throw(400, "Cannot delete tables which weren't created in Budibase.")
|
|
274
|
+
}
|
|
268
275
|
const datasourceId = getDatasourceId(tableToDelete)
|
|
269
276
|
|
|
270
277
|
const db = new CouchDB(appId)
|
package/src/api/routes/query.js
CHANGED
package/src/app.ts
CHANGED
|
@@ -41,16 +41,8 @@ app.use(
|
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
if (!env.isTest()) {
|
|
44
|
-
const
|
|
45
|
-
app.use(
|
|
46
|
-
if (ctx.path.startsWith(bullboard.pathPrefix)) {
|
|
47
|
-
ctx.status = 200
|
|
48
|
-
ctx.respond = false
|
|
49
|
-
bullApp(ctx.req, ctx.res)
|
|
50
|
-
} else {
|
|
51
|
-
await next()
|
|
52
|
-
}
|
|
53
|
-
})
|
|
44
|
+
const plugin = bullboard.init()
|
|
45
|
+
app.use(plugin)
|
|
54
46
|
}
|
|
55
47
|
|
|
56
48
|
app.context.eventEmitter = eventEmitter
|