@budibase/server 0.9.185-alpha.0 → 0.9.185-alpha.12
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 +1 -1
- package/builder/assets/bb-space-man.5be51a45.svg +290 -0
- package/builder/assets/index.4a7c5636.css +1 -0
- package/builder/assets/index.708945dd.js +1316 -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 +1060 -928
- 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 +31 -7
- 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 +75 -75
- 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 +12 -6
- package/coverage/lcov-report/api/controllers/row/index.html +18 -18
- 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 +65 -8
- package/coverage/lcov-report/api/controllers/table/index.html +42 -42
- package/coverage/lcov-report/api/controllers/table/index.js.html +66 -12
- package/coverage/lcov-report/api/controllers/table/internal.js.html +49 -10
- package/coverage/lcov-report/api/controllers/table/utils.js.html +57 -45
- package/coverage/lcov-report/api/controllers/templates.js.html +4 -4
- package/coverage/lcov-report/api/controllers/user.js.html +33 -18
- 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 +35 -2
- 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 +1 -1
- package/coverage/lcov-report/constants/index.js.html +5 -2
- 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 +1 -1
- package/coverage/lcov-report/db/utils.js.html +1 -1
- 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 +94 -79
- 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 +5 -5
- 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} +83 -32
- package/coverage/lcov-report/threads/index.html +141 -0
- package/coverage/lcov-report/threads/index.js.html +260 -0
- package/coverage/lcov-report/threads/query.js.html +269 -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 +144 -15
- package/coverage/lcov-report/utilities/global.js.html +2 -2
- package/coverage/lcov-report/utilities/index.html +34 -34
- 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 +1753 -1527
- package/dist/api/controllers/application.js +11 -1
- package/dist/api/controllers/datasource.js +6 -2
- 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/row/ExternalRequest.js +2 -2
- package/dist/api/controllers/row/external.js +1 -0
- package/dist/api/controllers/table/external.js +17 -0
- package/dist/api/controllers/table/index.js +21 -2
- package/dist/api/controllers/table/internal.js +10 -1
- package/dist/api/controllers/table/utils.js +28 -27
- package/dist/api/controllers/user.js +10 -6
- package/dist/api/routes/query.js +1 -1
- package/dist/api/routes/table.js +7 -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 +1 -0
- package/dist/definitions/datasource.js +1 -0
- package/dist/integrations/base/IntegrationBase.js +2 -0
- package/dist/integrations/base/sql.js +48 -8
- package/dist/integrations/base/sqlTable.js +1 -2
- package/dist/integrations/mysql.js +3 -1
- package/dist/integrations/postgres.js +2 -2
- package/dist/middleware/builder.js +4 -1
- package/dist/package.json +7 -6
- package/dist/{automations/thread.js → threads/automation.js} +23 -7
- package/dist/threads/index.js +58 -0
- package/dist/threads/query.js +61 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utilities/csvParser.js +46 -5
- 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 +8 -7
- package/scripts/likeCypress.ts +33 -0
- package/src/api/controllers/application.js +11 -1
- package/src/api/controllers/datasource.js +10 -2
- 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/row/ExternalRequest.ts +6 -2
- package/src/api/controllers/row/external.js +2 -0
- package/src/api/controllers/table/external.js +19 -0
- package/src/api/controllers/table/index.js +20 -2
- package/src/api/controllers/table/internal.js +14 -1
- package/src/api/controllers/table/utils.js +36 -32
- package/src/api/controllers/user.js +15 -10
- package/src/api/routes/query.js +1 -0
- package/src/api/routes/table.js +11 -0
- package/src/api/routes/tests/misc.spec.js +5 -1
- 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 +1 -0
- package/src/definitions/datasource.ts +3 -2
- 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 +51 -9
- package/src/integrations/base/sqlTable.ts +1 -4
- package/src/integrations/couchdb.ts +2 -1
- package/src/integrations/dynamodb.ts +2 -1
- package/src/integrations/elasticsearch.ts +2 -1
- package/src/integrations/mongodb.ts +2 -1
- package/src/integrations/mysql.ts +1 -1
- package/src/integrations/postgres.ts +5 -5
- package/src/integrations/rest.ts +2 -1
- package/src/integrations/s3.ts +2 -1
- package/src/middleware/builder.js +4 -1
- package/src/{automations/thread.js → threads/automation.js} +24 -7
- package/src/threads/index.js +60 -0
- package/src/threads/query.js +63 -0
- package/src/utilities/csvParser.js +48 -5
- package/src/utilities/index.js +1 -0
- package/src/utilities/queue/inMemoryQueue.js +7 -0
- package/src/utilities/scriptRunner.js +4 -1
- package/src/utilities/tests/__snapshots__/csvParser.spec.js.snap +0 -6
- package/src/utilities/tests/csvParser.spec.js +3 -0
- package/tsconfig.json +1 -1
- package/builder/assets/index.0e6a5c20.js +0 -201
- package/builder/assets/index.cf21e4d2.css +0 -29
- package/builder/assets/vendor.278a305c.js +0 -9
- package/builder/assets/vendor.d75d5eb7.css +0 -1
|
@@ -56,7 +56,7 @@ function parse(csvString, parsers) {
|
|
|
56
56
|
});
|
|
57
57
|
result.subscribe(row => {
|
|
58
58
|
// For each CSV row parse all the columns that need parsed
|
|
59
|
-
for (let key
|
|
59
|
+
for (let key of Object.keys(parsers)) {
|
|
60
60
|
if (!schema[key] || schema[key].success) {
|
|
61
61
|
// get the validator for the column type
|
|
62
62
|
const validator = VALIDATORS[parsers[key].type];
|
|
@@ -79,15 +79,55 @@ function parse(csvString, parsers) {
|
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
function
|
|
82
|
+
function updateSchema({ schema, existingTable }) {
|
|
83
|
+
if (!schema) {
|
|
84
|
+
return schema;
|
|
85
|
+
}
|
|
86
|
+
const finalSchema = {};
|
|
87
|
+
const schemaKeyMap = {};
|
|
88
|
+
Object.keys(schema).forEach(key => (schemaKeyMap[key.toLowerCase()] = key));
|
|
89
|
+
for (let [key, field] of Object.entries(existingTable.schema)) {
|
|
90
|
+
const lcKey = key.toLowerCase();
|
|
91
|
+
const foundKey = schemaKeyMap[lcKey];
|
|
92
|
+
if (foundKey) {
|
|
93
|
+
finalSchema[key] = schema[foundKey];
|
|
94
|
+
finalSchema[key].type = field.type;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return finalSchema;
|
|
98
|
+
}
|
|
99
|
+
function transform({ schema, csvString, existingTable }) {
|
|
83
100
|
return __awaiter(this, void 0, void 0, function* () {
|
|
84
101
|
const colParser = {};
|
|
85
|
-
|
|
102
|
+
// make sure the table has all the columns required for import
|
|
103
|
+
if (existingTable) {
|
|
104
|
+
schema = updateSchema({ schema, existingTable });
|
|
105
|
+
}
|
|
106
|
+
for (let key of Object.keys(schema)) {
|
|
86
107
|
colParser[key] = PARSERS[schema[key].type] || schema[key].type;
|
|
87
108
|
}
|
|
88
109
|
try {
|
|
89
|
-
const
|
|
90
|
-
|
|
110
|
+
const data = yield csv({ colParser }).fromString(csvString);
|
|
111
|
+
const schemaKeyMap = {};
|
|
112
|
+
Object.keys(schema).forEach(key => (schemaKeyMap[key.toLowerCase()] = key));
|
|
113
|
+
for (let element of data) {
|
|
114
|
+
if (!data) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
for (let key of Object.keys(element)) {
|
|
118
|
+
const mappedKey = schemaKeyMap[key.toLowerCase()];
|
|
119
|
+
// isn't a column in the table, remove it
|
|
120
|
+
if (mappedKey == null) {
|
|
121
|
+
delete element[key];
|
|
122
|
+
}
|
|
123
|
+
// casing is different, fix it in row
|
|
124
|
+
else if (key !== mappedKey) {
|
|
125
|
+
element[mappedKey] = element[key];
|
|
126
|
+
delete element[key];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return data;
|
|
91
131
|
}
|
|
92
132
|
catch (err) {
|
|
93
133
|
console.error(`Error transforming CSV to JSON for data import`, err);
|
|
@@ -98,4 +138,5 @@ function transform({ schema, csvString }) {
|
|
|
98
138
|
module.exports = {
|
|
99
139
|
parse,
|
|
100
140
|
transform,
|
|
141
|
+
updateSchema,
|
|
101
142
|
};
|
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.12",
|
|
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.12",
|
|
72
|
+
"@budibase/client": "^0.9.185-alpha.12",
|
|
73
|
+
"@budibase/string-templates": "^0.9.185-alpha.12",
|
|
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
|
},
|
|
@@ -138,7 +140,6 @@
|
|
|
138
140
|
"copyfiles": "^2.4.1",
|
|
139
141
|
"docker-compose": "^0.23.6",
|
|
140
142
|
"eslint": "^6.8.0",
|
|
141
|
-
"express": "^4.17.1",
|
|
142
143
|
"jest": "^27.0.5",
|
|
143
144
|
"nodemon": "^2.0.4",
|
|
144
145
|
"prettier": "^2.3.1",
|
|
@@ -149,5 +150,5 @@
|
|
|
149
150
|
"typescript": "^4.3.5",
|
|
150
151
|
"update-dotenv": "^1.1.1"
|
|
151
152
|
},
|
|
152
|
-
"gitHead": "
|
|
153
|
+
"gitHead": "c48ead6f4930f1dcaa2e6efc974bb38406e31e6d"
|
|
153
154
|
}
|
|
@@ -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) => {
|
|
@@ -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)
|
|
@@ -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
|
|
|
@@ -33,6 +33,7 @@ interface RunConfig {
|
|
|
33
33
|
sort?: SortJson
|
|
34
34
|
paginate?: PaginationJson
|
|
35
35
|
row?: Row
|
|
36
|
+
rows?: Row[]
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
module External {
|
|
@@ -600,7 +601,10 @@ module External {
|
|
|
600
601
|
throw `Unable to process query, table "${tableName}" not defined.`
|
|
601
602
|
}
|
|
602
603
|
// look for specific components of config which may not be considered acceptable
|
|
603
|
-
let { id, row, filters, sort, paginate } = cleanupConfig(
|
|
604
|
+
let { id, row, filters, sort, paginate, rows } = cleanupConfig(
|
|
605
|
+
config,
|
|
606
|
+
table
|
|
607
|
+
)
|
|
604
608
|
filters = buildFilters(id, filters || {}, table)
|
|
605
609
|
const relationships = this.buildRelationships(table)
|
|
606
610
|
// clean up row on ingress using schema
|
|
@@ -626,7 +630,7 @@ module External {
|
|
|
626
630
|
sort,
|
|
627
631
|
paginate,
|
|
628
632
|
relationships,
|
|
629
|
-
body: row,
|
|
633
|
+
body: row || rows,
|
|
630
634
|
// pass an id filter into extra, purely for mysql/returning
|
|
631
635
|
extra: {
|
|
632
636
|
idFilter: buildFilters(id || generateIdForRow(row, table), {}, table),
|
|
@@ -17,6 +17,8 @@ const {
|
|
|
17
17
|
} = require("../../../constants")
|
|
18
18
|
const { makeExternalQuery } = require("../../../integrations/base/utils")
|
|
19
19
|
const { cloneDeep } = require("lodash/fp")
|
|
20
|
+
const csvParser = require("../../../utilities/csvParser")
|
|
21
|
+
const { handleRequest } = require("../row/external")
|
|
20
22
|
|
|
21
23
|
async function makeTableRequest(
|
|
22
24
|
datasource,
|
|
@@ -279,3 +281,20 @@ exports.destroy = async function (ctx) {
|
|
|
279
281
|
|
|
280
282
|
return tableToDelete
|
|
281
283
|
}
|
|
284
|
+
|
|
285
|
+
exports.bulkImport = async function (ctx) {
|
|
286
|
+
const appId = ctx.appId
|
|
287
|
+
const table = await getTable(appId, ctx.params.tableId)
|
|
288
|
+
const { dataImport } = ctx.request.body
|
|
289
|
+
if (!dataImport || !dataImport.schema || !dataImport.csvString) {
|
|
290
|
+
ctx.throw(400, "Provided data import information is invalid.")
|
|
291
|
+
}
|
|
292
|
+
const rows = await csvParser.transform({
|
|
293
|
+
...dataImport,
|
|
294
|
+
existingTable: table,
|
|
295
|
+
})
|
|
296
|
+
await handleRequest(appId, DataSourceOperation.BULK_CREATE, table._id, {
|
|
297
|
+
rows,
|
|
298
|
+
})
|
|
299
|
+
return table
|
|
300
|
+
}
|
|
@@ -81,8 +81,26 @@ exports.destroy = async function (ctx) {
|
|
|
81
81
|
ctx.body = { message: `Table ${tableId} deleted.` }
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
exports.bulkImport = async function (ctx) {
|
|
85
|
+
const tableId = ctx.params.tableId
|
|
86
|
+
await pickApi({ tableId }).bulkImport(ctx)
|
|
87
|
+
// right now we don't trigger anything for bulk import because it
|
|
88
|
+
// can only be done in the builder, but in the future we may need to
|
|
89
|
+
// think about events for bulk items
|
|
90
|
+
ctx.status = 200
|
|
91
|
+
ctx.body = { message: `Bulk rows created.` }
|
|
92
|
+
}
|
|
93
|
+
|
|
84
94
|
exports.validateCSVSchema = async function (ctx) {
|
|
85
|
-
|
|
86
|
-
const
|
|
95
|
+
// tableId being specified means its an import to an existing table
|
|
96
|
+
const { csvString, schema = {}, tableId } = ctx.request.body
|
|
97
|
+
let existingTable
|
|
98
|
+
if (tableId) {
|
|
99
|
+
existingTable = await getTable(ctx.appId, tableId)
|
|
100
|
+
}
|
|
101
|
+
let result = await csvParser.parse(csvString, schema)
|
|
102
|
+
if (existingTable) {
|
|
103
|
+
result = csvParser.updateSchema({ schema: result, existingTable })
|
|
104
|
+
}
|
|
87
105
|
ctx.body = { schema: result }
|
|
88
106
|
}
|
|
@@ -2,7 +2,12 @@ const CouchDB = require("../../../db")
|
|
|
2
2
|
const linkRows = require("../../../db/linkedRows")
|
|
3
3
|
const { getRowParams, generateTableID } = require("../../../db/utils")
|
|
4
4
|
const { FieldTypes } = require("../../../constants")
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
TableSaveFunctions,
|
|
7
|
+
hasTypeChanged,
|
|
8
|
+
getTable,
|
|
9
|
+
handleDataImport,
|
|
10
|
+
} = require("./utils")
|
|
6
11
|
|
|
7
12
|
exports.save = async function (ctx) {
|
|
8
13
|
const appId = ctx.appId
|
|
@@ -140,3 +145,11 @@ exports.destroy = async function (ctx) {
|
|
|
140
145
|
|
|
141
146
|
return tableToDelete
|
|
142
147
|
}
|
|
148
|
+
|
|
149
|
+
exports.bulkImport = async function (ctx) {
|
|
150
|
+
const appId = ctx.appId
|
|
151
|
+
const table = await getTable(appId, ctx.params.tableId)
|
|
152
|
+
const { dataImport } = ctx.request.body
|
|
153
|
+
await handleDataImport(appId, ctx.user, table, dataImport)
|
|
154
|
+
return table
|
|
155
|
+
}
|
|
@@ -72,43 +72,47 @@ exports.makeSureTableUpToDate = (table, tableToSave) => {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
exports.handleDataImport = async (appId, user, table, dataImport) => {
|
|
75
|
+
if (!dataImport || !dataImport.csvString) {
|
|
76
|
+
return table
|
|
77
|
+
}
|
|
75
78
|
const db = new CouchDB(appId)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
schema.constraints.inclusion
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
79
|
+
// Populate the table with rows imported from CSV in a bulk update
|
|
80
|
+
const data = await csvParser.transform({
|
|
81
|
+
...dataImport,
|
|
82
|
+
existingTable: table,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
let finalData = []
|
|
86
|
+
for (let i = 0; i < data.length; i++) {
|
|
87
|
+
let row = data[i]
|
|
88
|
+
row._id = generateRowID(table._id)
|
|
89
|
+
row.tableId = table._id
|
|
90
|
+
const processed = inputProcessing(user, table, row, {
|
|
91
|
+
noAutoRelationships: true,
|
|
92
|
+
})
|
|
93
|
+
table = processed.table
|
|
94
|
+
row = processed.row
|
|
95
|
+
|
|
96
|
+
for (let [fieldName, schema] of Object.entries(table.schema)) {
|
|
97
|
+
// check whether the options need to be updated for inclusion as part of the data import
|
|
98
|
+
if (
|
|
99
|
+
schema.type === FieldTypes.OPTIONS &&
|
|
100
|
+
(!schema.constraints.inclusion ||
|
|
101
|
+
schema.constraints.inclusion.indexOf(row[fieldName]) === -1)
|
|
102
|
+
) {
|
|
103
|
+
schema.constraints.inclusion = [
|
|
104
|
+
...schema.constraints.inclusion,
|
|
105
|
+
row[fieldName],
|
|
106
|
+
]
|
|
103
107
|
}
|
|
104
|
-
|
|
105
|
-
finalData.push(row)
|
|
106
108
|
}
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
let response = await db.put(table)
|
|
110
|
-
table._rev = response._rev
|
|
110
|
+
finalData.push(row)
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
await db.bulkDocs(finalData)
|
|
114
|
+
let response = await db.put(table)
|
|
115
|
+
table._rev = response._rev
|
|
112
116
|
return table
|
|
113
117
|
}
|
|
114
118
|
|