@budibase/server 2.7.23 → 2.7.25-alpha.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.7.23",
4
+ "version": "2.7.25-alpha.0",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -46,12 +46,12 @@
46
46
  "license": "GPL-3.0",
47
47
  "dependencies": {
48
48
  "@apidevtools/swagger-parser": "10.0.3",
49
- "@budibase/backend-core": "2.7.23",
50
- "@budibase/client": "2.7.23",
51
- "@budibase/pro": "2.7.23",
52
- "@budibase/shared-core": "2.7.23",
53
- "@budibase/string-templates": "2.7.23",
54
- "@budibase/types": "2.7.23",
49
+ "@budibase/backend-core": "2.7.25-alpha.0",
50
+ "@budibase/client": "2.7.25-alpha.0",
51
+ "@budibase/pro": "2.7.25-alpha.0",
52
+ "@budibase/shared-core": "2.7.25-alpha.0",
53
+ "@budibase/string-templates": "2.7.25-alpha.0",
54
+ "@budibase/types": "2.7.25-alpha.0",
55
55
  "@bull-board/api": "3.7.0",
56
56
  "@bull-board/koa": "3.9.4",
57
57
  "@elastic/elasticsearch": "7.10.0",
@@ -97,7 +97,7 @@
97
97
  "koa2-ratelimit": "1.1.1",
98
98
  "lodash": "4.17.21",
99
99
  "memorystream": "0.3.1",
100
- "mongodb": "4.9",
100
+ "mongodb": "5.6",
101
101
  "mssql": "6.2.3",
102
102
  "mysql2": "2.3.3",
103
103
  "node-fetch": "2.6.7",
@@ -117,7 +117,7 @@
117
117
  "socket.io": "4.6.1",
118
118
  "svelte": "3.49.0",
119
119
  "swagger-parser": "10.0.3",
120
- "tar": "6.1.11",
120
+ "tar": "6.1.15",
121
121
  "to-json-schema": "0.2.5",
122
122
  "uuid": "3.3.2",
123
123
  "validate.js": "0.13.1",
@@ -150,7 +150,7 @@
150
150
  "@types/redis": "4.0.11",
151
151
  "@types/server-destroy": "1.0.1",
152
152
  "@types/supertest": "2.0.12",
153
- "@types/tar": "6.1.3",
153
+ "@types/tar": "6.1.5",
154
154
  "@typescript-eslint/parser": "5.45.0",
155
155
  "apidoc": "0.50.4",
156
156
  "babel-jest": "29.5.0",
@@ -195,5 +195,5 @@
195
195
  }
196
196
  }
197
197
  },
198
- "gitHead": "807e9a80d749b8beac7fc57d38499b59036fdeb7"
198
+ "gitHead": "8ecb20caca227224ab8bfd29f92b99ab7077a9c0"
199
199
  }
@@ -1,17 +1,31 @@
1
1
  import sdk from "../../sdk"
2
- import { events, context } from "@budibase/backend-core"
2
+ import { events, context, db } from "@budibase/backend-core"
3
3
  import { DocumentType } from "../../db/utils"
4
- import { isQsTrue } from "../../utilities"
4
+ import { Ctx } from "@budibase/types"
5
+
6
+ interface ExportAppDumpRequest {
7
+ excludeRows: boolean
8
+ encryptPassword?: string
9
+ }
10
+
11
+ export async function exportAppDump(ctx: Ctx<ExportAppDumpRequest>) {
12
+ const { appId } = ctx.query as any
13
+ const { excludeRows, encryptPassword } = ctx.request.body
14
+
15
+ const [app] = await db.getAppsByIDs([appId])
16
+ const appName = app.name
5
17
 
6
- export async function exportAppDump(ctx: any) {
7
- let { appId, excludeRows } = ctx.query
8
18
  // remove the 120 second limit for the request
9
19
  ctx.req.setTimeout(0)
10
- const appName = decodeURI(ctx.query.appname)
11
- excludeRows = isQsTrue(excludeRows)
12
- const backupIdentifier = `${appName}-export-${new Date().getTime()}.tar.gz`
20
+
21
+ const extension = encryptPassword ? "enc.tar.gz" : "tar.gz"
22
+ const backupIdentifier = `${appName}-export-${new Date().getTime()}.${extension}`
13
23
  ctx.attachment(backupIdentifier)
14
- ctx.body = await sdk.backups.streamExportApp(appId, excludeRows)
24
+ ctx.body = await sdk.backups.streamExportApp({
25
+ appId,
26
+ excludeRows,
27
+ encryptPassword,
28
+ })
15
29
 
16
30
  await context.doInAppContext(appId, async () => {
17
31
  const appDb = context.getAppDB()
@@ -11,7 +11,7 @@ import { BuildSchemaErrors, InvalidColumns } from "../../constants"
11
11
  import { getIntegration } from "../../integrations"
12
12
  import { getDatasourceAndQuery } from "./row/utils"
13
13
  import { invalidateDynamicVariables } from "../../threads/utils"
14
- import { db as dbCore, context, events } from "@budibase/backend-core"
14
+ import { db as dbCore, context, events, cache } from "@budibase/backend-core"
15
15
  import {
16
16
  UserCtx,
17
17
  Datasource,
@@ -25,9 +25,11 @@ import {
25
25
  FetchDatasourceInfoResponse,
26
26
  IntegrationBase,
27
27
  DatasourcePlus,
28
+ SourceName,
28
29
  } from "@budibase/types"
29
30
  import sdk from "../../sdk"
30
31
  import { builderSocket } from "../../websockets"
32
+ import { setupCreationAuth as googleSetupCreationAuth } from "../../integrations/googlesheets"
31
33
 
32
34
  function getErrorTables(errors: any, errorType: string) {
33
35
  return Object.entries(errors)
@@ -101,6 +103,22 @@ async function buildSchemaHelper(datasource: Datasource) {
101
103
  return { tables: connector.tables, error }
102
104
  }
103
105
 
106
+ async function buildFilteredSchema(datasource: Datasource, filter?: string[]) {
107
+ let { tables, error } = await buildSchemaHelper(datasource)
108
+ let finalTables = tables
109
+ if (filter) {
110
+ finalTables = {}
111
+ for (let key in tables) {
112
+ if (
113
+ filter.some((filter: any) => filter.toLowerCase() === key.toLowerCase())
114
+ ) {
115
+ finalTables[key] = tables[key]
116
+ }
117
+ }
118
+ }
119
+ return { tables: finalTables, error }
120
+ }
121
+
104
122
  export async function fetch(ctx: UserCtx) {
105
123
  // Get internal tables
106
124
  const db = context.getAppDB()
@@ -172,43 +190,28 @@ export async function information(
172
190
  }
173
191
  const tableNames = await connector.getTableNames()
174
192
  ctx.body = {
175
- tableNames,
193
+ tableNames: tableNames.sort(),
176
194
  }
177
195
  }
178
196
 
179
197
  export async function buildSchemaFromDb(ctx: UserCtx) {
180
198
  const db = context.getAppDB()
181
- const datasource = await sdk.datasources.get(ctx.params.datasourceId)
182
199
  const tablesFilter = ctx.request.body.tablesFilter
200
+ const datasource = await sdk.datasources.get(ctx.params.datasourceId)
183
201
 
184
- let { tables, error } = await buildSchemaHelper(datasource)
185
- if (tablesFilter) {
186
- if (!datasource.entities) {
187
- datasource.entities = {}
188
- }
189
- for (let key in tables) {
190
- if (
191
- tablesFilter.some(
192
- (filter: any) => filter.toLowerCase() === key.toLowerCase()
193
- )
194
- ) {
195
- datasource.entities[key] = tables[key]
196
- }
197
- }
198
- } else {
199
- datasource.entities = tables
200
- }
202
+ const { tables, error } = await buildFilteredSchema(datasource, tablesFilter)
203
+ datasource.entities = tables
201
204
 
202
205
  setDefaultDisplayColumns(datasource)
203
206
  const dbResp = await db.put(datasource)
204
207
  datasource._rev = dbResp.rev
205
208
  const cleanedDatasource = await sdk.datasources.removeSecretSingle(datasource)
206
209
 
207
- const response: any = { datasource: cleanedDatasource }
210
+ const res: any = { datasource: cleanedDatasource }
208
211
  if (error) {
209
- response.error = error
212
+ res.error = error
210
213
  }
211
- ctx.body = response
214
+ ctx.body = res
212
215
  }
213
216
 
214
217
  /**
@@ -306,12 +309,19 @@ export async function update(ctx: UserCtx<any, UpdateDatasourceResponse>) {
306
309
  builderSocket?.emitDatasourceUpdate(ctx, datasource)
307
310
  }
308
311
 
312
+ const preSaveAction: Partial<Record<SourceName, any>> = {
313
+ [SourceName.GOOGLE_SHEETS]: async (datasource: Datasource) => {
314
+ await googleSetupCreationAuth(datasource.config as any)
315
+ },
316
+ }
317
+
309
318
  export async function save(
310
319
  ctx: UserCtx<CreateDatasourceRequest, CreateDatasourceResponse>
311
320
  ) {
312
321
  const db = context.getAppDB()
313
322
  const plus = ctx.request.body.datasource.plus
314
323
  const fetchSchema = ctx.request.body.fetchSchema
324
+ const tablesFilter = ctx.request.body.tablesFilter
315
325
 
316
326
  const datasource = {
317
327
  _id: generateDatasourceID({ plus }),
@@ -321,12 +331,19 @@ export async function save(
321
331
 
322
332
  let schemaError = null
323
333
  if (fetchSchema) {
324
- const { tables, error } = await buildSchemaHelper(datasource)
334
+ const { tables, error } = await buildFilteredSchema(
335
+ datasource,
336
+ tablesFilter
337
+ )
325
338
  schemaError = error
326
339
  datasource.entities = tables
327
340
  setDefaultDisplayColumns(datasource)
328
341
  }
329
342
 
343
+ if (preSaveAction[datasource.source]) {
344
+ await preSaveAction[datasource.source](datasource)
345
+ }
346
+
330
347
  const dbResp = await db.put(datasource)
331
348
  await events.datasource.created(datasource)
332
349
  datasource._rev = dbResp.rev
@@ -4,7 +4,7 @@ import {
4
4
  getUserMetadataParams,
5
5
  InternalTables,
6
6
  } from "../../db/utils"
7
- import { BBContext, Database } from "@budibase/types"
7
+ import { UserCtx, Database } from "@budibase/types"
8
8
 
9
9
  const UpdateRolesOptions = {
10
10
  CREATED: "created",
@@ -38,15 +38,15 @@ async function updateRolesOnUserTable(
38
38
  }
39
39
  }
40
40
 
41
- export async function fetch(ctx: BBContext) {
41
+ export async function fetch(ctx: UserCtx) {
42
42
  ctx.body = await roles.getAllRoles()
43
43
  }
44
44
 
45
- export async function find(ctx: BBContext) {
45
+ export async function find(ctx: UserCtx) {
46
46
  ctx.body = await roles.getRole(ctx.params.roleId)
47
47
  }
48
48
 
49
- export async function save(ctx: BBContext) {
49
+ export async function save(ctx: UserCtx) {
50
50
  const db = context.getAppDB()
51
51
  let { _id, name, inherits, permissionId } = ctx.request.body
52
52
  let isCreate = false
@@ -72,7 +72,7 @@ export async function save(ctx: BBContext) {
72
72
  ctx.message = `Role '${role.name}' created successfully.`
73
73
  }
74
74
 
75
- export async function destroy(ctx: BBContext) {
75
+ export async function destroy(ctx: UserCtx) {
76
76
  const db = context.getAppDB()
77
77
  const roleId = ctx.params.roleId
78
78
  const role = await db.get(roleId)
@@ -1,6 +1,6 @@
1
1
  import { getRoutingInfo } from "../../utilities/routing"
2
2
  import { roles } from "@budibase/backend-core"
3
- import { BBContext } from "@budibase/types"
3
+ import { UserCtx } from "@budibase/types"
4
4
 
5
5
  const URL_SEPARATOR = "/"
6
6
 
@@ -56,11 +56,11 @@ async function getRoutingStructure() {
56
56
  return { routes: routing.json }
57
57
  }
58
58
 
59
- export async function fetch(ctx: BBContext) {
59
+ export async function fetch(ctx: UserCtx) {
60
60
  ctx.body = await getRoutingStructure()
61
61
  }
62
62
 
63
- export async function clientFetch(ctx: BBContext) {
63
+ export async function clientFetch(ctx: UserCtx) {
64
64
  const routing = await getRoutingStructure()
65
65
  let roleId = ctx.user?.role?._id
66
66
  const roleIds = (await roles.getUserRoleHierarchy(roleId, {
@@ -5,7 +5,7 @@ import { permissions } from "@budibase/backend-core"
5
5
 
6
6
  const router: Router = new Router()
7
7
 
8
- router.get(
8
+ router.post(
9
9
  "/api/backups/export",
10
10
  authorized(permissions.BUILDER),
11
11
  controller.exportAppDump
@@ -1,7 +1,9 @@
1
+ import tk from "timekeeper"
1
2
  import * as setup from "./utilities"
2
3
  import { events } from "@budibase/backend-core"
3
4
  import sdk from "../../../sdk"
4
5
  import { checkBuilderEndpoint } from "./utilities/TestFunctions"
6
+ import { mocks } from "@budibase/backend-core/tests"
5
7
 
6
8
  describe("/backups", () => {
7
9
  let request = setup.getRequest()
@@ -16,7 +18,7 @@ describe("/backups", () => {
16
18
  describe("exportAppDump", () => {
17
19
  it("should be able to export app", async () => {
18
20
  const res = await request
19
- .get(`/api/backups/export?appId=${config.getAppId()}&appname=test`)
21
+ .post(`/api/backups/export?appId=${config.getAppId()}`)
20
22
  .set(config.defaultHeaders())
21
23
  .expect(200)
22
24
  expect(res.headers["content-type"]).toEqual("application/gzip")
@@ -26,10 +28,24 @@ describe("/backups", () => {
26
28
  it("should apply authorization to endpoint", async () => {
27
29
  await checkBuilderEndpoint({
28
30
  config,
29
- method: "GET",
31
+ method: "POST",
30
32
  url: `/api/backups/export?appId=${config.getAppId()}`,
31
33
  })
32
34
  })
35
+
36
+ it("should infer the app name from the app", async () => {
37
+ tk.freeze(mocks.date.MOCK_DATE)
38
+
39
+ const res = await request
40
+ .post(`/api/backups/export?appId=${config.getAppId()}`)
41
+ .set(config.defaultHeaders())
42
+
43
+ expect(res.headers["content-disposition"]).toEqual(
44
+ `attachment; filename="${
45
+ config.getApp()!.name
46
+ }-export-${mocks.date.MOCK_DATE.getTime()}.tar.gz"`
47
+ )
48
+ })
33
49
  })
34
50
 
35
51
  describe("calculateBackupStats", () => {
@@ -48,6 +48,35 @@ export const definition: AutomationStepSchema = {
48
48
  type: AutomationIOType.STRING,
49
49
  title: "HTML Contents",
50
50
  },
51
+ addInvite: {
52
+ type: AutomationIOType.BOOLEAN,
53
+ title: "Add calendar invite",
54
+ },
55
+ startTime: {
56
+ type: AutomationIOType.DATE,
57
+ title: "Start Time",
58
+ dependsOn: "addInvite",
59
+ },
60
+ endTime: {
61
+ type: AutomationIOType.DATE,
62
+ title: "End Time",
63
+ dependsOn: "addInvite",
64
+ },
65
+ summary: {
66
+ type: AutomationIOType.STRING,
67
+ title: "Meeting Summary",
68
+ dependsOn: "addInvite",
69
+ },
70
+ location: {
71
+ type: AutomationIOType.STRING,
72
+ title: "Location",
73
+ dependsOn: "addInvite",
74
+ },
75
+ url: {
76
+ type: AutomationIOType.STRING,
77
+ title: "URL",
78
+ dependsOn: "addInvite",
79
+ },
51
80
  },
52
81
  required: ["to", "from", "subject", "contents"],
53
82
  },
@@ -68,21 +97,43 @@ export const definition: AutomationStepSchema = {
68
97
  }
69
98
 
70
99
  export async function run({ inputs }: AutomationStepInput) {
71
- let { to, from, subject, contents, cc, bcc } = inputs
100
+ let {
101
+ to,
102
+ from,
103
+ subject,
104
+ contents,
105
+ cc,
106
+ bcc,
107
+ addInvite,
108
+ startTime,
109
+ endTime,
110
+ summary,
111
+ location,
112
+ url,
113
+ } = inputs
72
114
  if (!contents) {
73
115
  contents = "<h1>No content</h1>"
74
116
  }
75
117
  to = to || undefined
76
118
  try {
77
- let response = await sendSmtpEmail(
119
+ let response = await sendSmtpEmail({
78
120
  to,
79
121
  from,
80
122
  subject,
81
123
  contents,
82
124
  cc,
83
125
  bcc,
84
- true
85
- )
126
+ automation: true,
127
+ invite: addInvite
128
+ ? {
129
+ startTime,
130
+ endTime,
131
+ summary,
132
+ location,
133
+ url,
134
+ }
135
+ : undefined,
136
+ })
86
137
  return {
87
138
  success: true,
88
139
  response,
@@ -0,0 +1,74 @@
1
+ import * as workerRequests from "../../utilities/workerRequests"
2
+
3
+ jest.mock("../../utilities/workerRequests", () => ({
4
+ sendSmtpEmail: jest.fn(),
5
+ }))
6
+
7
+ function generateResponse(to: string, from: string) {
8
+ return {
9
+ success: true,
10
+ response: {
11
+ accepted: [to],
12
+ envelope: {
13
+ from: from,
14
+ to: [to],
15
+ },
16
+ message: `Email sent to ${to}.`,
17
+ },
18
+ }
19
+ }
20
+
21
+ const setup = require("./utilities")
22
+
23
+ describe("test the outgoing webhook action", () => {
24
+ let inputs
25
+ let config = setup.getConfig()
26
+ beforeAll(async () => {
27
+ await config.init()
28
+ })
29
+
30
+ afterAll(setup.afterAll)
31
+
32
+ it("should be able to run the action", async () => {
33
+ jest
34
+ .spyOn(workerRequests, "sendSmtpEmail")
35
+ .mockImplementationOnce(async () =>
36
+ generateResponse("user1@test.com", "admin@test.com")
37
+ )
38
+ const invite = {
39
+ startTime: new Date(),
40
+ endTime: new Date(),
41
+ summary: "summary",
42
+ location: "location",
43
+ url: "url",
44
+ }
45
+ inputs = {
46
+ to: "user1@test.com",
47
+ from: "admin@test.com",
48
+ subject: "hello",
49
+ contents: "testing",
50
+ cc: "cc",
51
+ bcc: "bcc",
52
+ addInvite: true,
53
+ ...invite,
54
+ }
55
+ let resp = generateResponse(inputs.to, inputs.from)
56
+ const res = await setup.runStep(
57
+ setup.actions.SEND_EMAIL_SMTP.stepId,
58
+ inputs
59
+ )
60
+ expect(res.response).toEqual(resp)
61
+ expect(res.success).toEqual(true)
62
+ expect(workerRequests.sendSmtpEmail).toHaveBeenCalledTimes(1)
63
+ expect(workerRequests.sendSmtpEmail).toHaveBeenCalledWith({
64
+ to: "user1@test.com",
65
+ from: "admin@test.com",
66
+ subject: "hello",
67
+ contents: "testing",
68
+ cc: "cc",
69
+ bcc: "bcc",
70
+ invite,
71
+ automation: true,
72
+ })
73
+ })
74
+ })
@@ -26,6 +26,10 @@ export default function process(updateCb?: UpdateCallback) {
26
26
  // if something not found - no changes to perform
27
27
  if (err?.status === 404) {
28
28
  return
29
+ }
30
+ // The user has already been sync in another process
31
+ else if (err?.status === 409) {
32
+ return
29
33
  } else {
30
34
  logging.logAlert("Failed to perform user/group app sync", err)
31
35
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ConnectionInfo,
3
+ Datasource,
3
4
  DatasourceFeature,
4
5
  DatasourceFieldType,
5
6
  DatasourcePlus,
@@ -19,13 +20,15 @@ import { OAuth2Client } from "google-auth-library"
19
20
  import { buildExternalTableId, finaliseExternalTables } from "./utils"
20
21
  import { GoogleSpreadsheet, GoogleSpreadsheetRow } from "google-spreadsheet"
21
22
  import fetch from "node-fetch"
22
- import { configs, HTTPError } from "@budibase/backend-core"
23
- import { dataFilters } from "@budibase/shared-core"
23
+ import { cache, configs, context, HTTPError } from "@budibase/backend-core"
24
+ import { dataFilters, utils } from "@budibase/shared-core"
24
25
  import { GOOGLE_SHEETS_PRIMARY_KEY } from "../constants"
26
+ import sdk from "../sdk"
25
27
 
26
28
  interface GoogleSheetsConfig {
27
29
  spreadsheetId: string
28
30
  auth: OAuthClientConfig
31
+ continueSetupId?: string
29
32
  }
30
33
 
31
34
  interface OAuthClientConfig {
@@ -72,7 +75,7 @@ const SCHEMA: Integration = {
72
75
  },
73
76
  datasource: {
74
77
  spreadsheetId: {
75
- display: "Google Sheet URL",
78
+ display: "Spreadsheet URL",
76
79
  type: DatasourceFieldType.STRING,
77
80
  required: true,
78
81
  },
@@ -207,6 +210,8 @@ class GoogleSheetsIntegration implements DatasourcePlus {
207
210
 
208
211
  async connect() {
209
212
  try {
213
+ await setupCreationAuth(this.config)
214
+
210
215
  // Initialise oAuth client
211
216
  let googleConfig = await configs.getGoogleDatasourceConfig()
212
217
  if (!googleConfig) {
@@ -269,24 +274,24 @@ class GoogleSheetsIntegration implements DatasourcePlus {
269
274
  }
270
275
 
271
276
  async buildSchema(datasourceId: string, entities: Record<string, Table>) {
272
- // not fully configured yet
273
- if (!this.config.auth) {
274
- return
275
- }
276
277
  await this.connect()
277
278
  const sheets = this.client.sheetsByIndex
278
279
  const tables: Record<string, Table> = {}
279
- for (let sheet of sheets) {
280
- // must fetch rows to determine schema
281
- await sheet.getRows()
282
-
283
- const id = buildExternalTableId(datasourceId, sheet.title)
284
- tables[sheet.title] = this.getTableSchema(
285
- sheet.title,
286
- sheet.headerValues,
287
- id
288
- )
289
- }
280
+ await utils.parallelForeach(
281
+ sheets,
282
+ async sheet => {
283
+ // must fetch rows to determine schema
284
+ await sheet.getRows({ limit: 0, offset: 0 })
285
+
286
+ const id = buildExternalTableId(datasourceId, sheet.title)
287
+ tables[sheet.title] = this.getTableSchema(
288
+ sheet.title,
289
+ sheet.headerValues,
290
+ id
291
+ )
292
+ },
293
+ 10
294
+ )
290
295
  const final = finaliseExternalTables(tables, entities)
291
296
  this.tables = final.tables
292
297
  this.schemaErrors = final.errors
@@ -566,6 +571,18 @@ class GoogleSheetsIntegration implements DatasourcePlus {
566
571
  }
567
572
  }
568
573
 
574
+ export async function setupCreationAuth(datasouce: GoogleSheetsConfig) {
575
+ if (datasouce.continueSetupId) {
576
+ const appId = context.getAppId()
577
+ const tokens = await cache.get(
578
+ `datasource:creation:${appId}:google:${datasouce.continueSetupId}`
579
+ )
580
+
581
+ datasouce.auth = tokens.tokens
582
+ delete datasouce.continueSetupId
583
+ }
584
+ }
585
+
569
586
  export default {
570
587
  schema: SCHEMA,
571
588
  integration: GoogleSheetsIntegration,
@@ -351,7 +351,7 @@ const SCHEMA: Integration = getSchema()
351
351
 
352
352
  class MongoIntegration implements IntegrationBase {
353
353
  private config: MongoDBConfig
354
- private client: any
354
+ private client: MongoClient
355
355
 
356
356
  constructor(config: MongoDBConfig) {
357
357
  this.config = config
@@ -372,6 +372,8 @@ class MongoIntegration implements IntegrationBase {
372
372
  response.connected = true
373
373
  } catch (e: any) {
374
374
  response.error = e.message as string
375
+ } finally {
376
+ await this.client.close()
375
377
  }
376
378
  return response
377
379
  }
@@ -380,7 +382,7 @@ class MongoIntegration implements IntegrationBase {
380
382
  return this.client.connect()
381
383
  }
382
384
 
383
- createObjectIds(json: any): object {
385
+ createObjectIds(json: any) {
384
386
  const self = this
385
387
  function interpolateObjectIds(json: any) {
386
388
  for (let field of Object.keys(json)) {