@budibase/server 2.6.17 → 2.6.19-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.
Files changed (62) hide show
  1. package/builder/assets/{index.86c992bf.css → index.07382a47.css} +2 -2
  2. package/builder/assets/{index.a40dcadd.js → index.b9eeb2a8.js} +307 -315
  3. package/builder/index.html +2 -2
  4. package/dist/api/controllers/datasource.js +70 -39
  5. package/dist/api/controllers/integration.js +2 -2
  6. package/dist/api/routes/datasource.js +1 -0
  7. package/dist/automations/steps/make.js +19 -5
  8. package/dist/automations/steps/zapier.js +19 -6
  9. package/dist/automations/utils.js +13 -7
  10. package/dist/db/dynamoClient.js +1 -1
  11. package/dist/integrations/airtable.js +28 -2
  12. package/dist/integrations/arangodb.js +19 -3
  13. package/dist/integrations/couchdb.js +16 -1
  14. package/dist/integrations/dynamodb.js +16 -0
  15. package/dist/integrations/elasticsearch.js +15 -0
  16. package/dist/integrations/firebase.js +15 -0
  17. package/dist/integrations/googlesheets.js +30 -1
  18. package/dist/integrations/index.js +5 -2
  19. package/dist/integrations/microsoftSqlServer.js +16 -0
  20. package/dist/integrations/mongodb.js +16 -0
  21. package/dist/integrations/mysql.js +21 -5
  22. package/dist/integrations/oracle.js +29 -0
  23. package/dist/integrations/postgres.js +26 -7
  24. package/dist/integrations/redis.js +20 -1
  25. package/dist/integrations/s3.js +23 -4
  26. package/dist/integrations/snowflake.js +15 -0
  27. package/dist/migrations/functions/backfill/app/queries.js +1 -2
  28. package/dist/sdk/app/datasources/datasources.js +10 -3
  29. package/dist/tsconfig.build.tsbuildinfo +1 -1
  30. package/jest.config.ts +3 -3
  31. package/nodemon.json +7 -3
  32. package/package.json +10 -9
  33. package/src/api/controllers/datasource.ts +88 -49
  34. package/src/api/controllers/integration.ts +3 -3
  35. package/src/api/routes/datasource.ts +5 -0
  36. package/src/automations/steps/make.ts +18 -1
  37. package/src/automations/steps/zapier.ts +18 -1
  38. package/src/automations/tests/make.spec.ts +54 -0
  39. package/src/automations/tests/zapier.spec.ts +56 -0
  40. package/src/automations/utils.ts +13 -7
  41. package/src/db/dynamoClient.ts +1 -1
  42. package/src/integration-test/postgres.spec.ts +0 -1
  43. package/src/integrations/airtable.ts +31 -4
  44. package/src/integrations/arangodb.ts +18 -2
  45. package/src/integrations/couchdb.ts +18 -4
  46. package/src/integrations/dynamodb.ts +34 -5
  47. package/src/integrations/elasticsearch.ts +16 -1
  48. package/src/integrations/firebase.ts +15 -0
  49. package/src/integrations/googlesheets.ts +31 -2
  50. package/src/integrations/index.ts +12 -7
  51. package/src/integrations/microsoftSqlServer.ts +16 -0
  52. package/src/integrations/mongodb.ts +16 -0
  53. package/src/integrations/mysql.ts +27 -16
  54. package/src/integrations/oracle.ts +28 -6
  55. package/src/integrations/postgres.ts +21 -3
  56. package/src/integrations/redis.ts +26 -3
  57. package/src/integrations/s3.ts +19 -3
  58. package/src/integrations/snowflake.ts +20 -1
  59. package/src/migrations/functions/backfill/app/queries.ts +1 -1
  60. package/src/sdk/app/datasources/datasources.ts +7 -1
  61. package/tsconfig.json +1 -1
  62. package/src/automations/tests/zapier.spec.js +0 -27
@@ -3,10 +3,13 @@ import {
3
3
  DatasourceFieldType,
4
4
  QueryType,
5
5
  IntegrationBase,
6
+ DatasourceFeature,
7
+ ConnectionInfo,
6
8
  } from "@budibase/types"
7
9
 
8
10
  import AWS from "aws-sdk"
9
11
  import { AWS_REGION } from "../db/dynamoClient"
12
+ import { DocumentClient } from "aws-sdk/clients/dynamodb"
10
13
 
11
14
  interface DynamoDBConfig {
12
15
  region: string
@@ -22,6 +25,7 @@ const SCHEMA: Integration = {
22
25
  "Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.",
23
26
  friendlyName: "DynamoDB",
24
27
  type: "Non-relational",
28
+ features: [DatasourceFeature.CONNECTION_CHECKING],
25
29
  datasource: {
26
30
  region: {
27
31
  type: DatasourceFieldType.STRING,
@@ -128,7 +132,7 @@ const SCHEMA: Integration = {
128
132
 
129
133
  class DynamoDBIntegration implements IntegrationBase {
130
134
  private config: DynamoDBConfig
131
- private client: any
135
+ private client
132
136
 
133
137
  constructor(config: DynamoDBConfig) {
134
138
  this.config = config
@@ -148,7 +152,23 @@ class DynamoDBIntegration implements IntegrationBase {
148
152
  this.client = new AWS.DynamoDB.DocumentClient(this.config)
149
153
  }
150
154
 
151
- async create(query: { table: string; json: object }) {
155
+ async testConnection() {
156
+ const response: ConnectionInfo = {
157
+ connected: false,
158
+ }
159
+ try {
160
+ const scanRes = await new AWS.DynamoDB(this.config).listTables().promise()
161
+ response.connected = !!scanRes.$response
162
+ } catch (e: any) {
163
+ response.error = e.message as string
164
+ }
165
+ return response
166
+ }
167
+
168
+ async create(query: {
169
+ table: string
170
+ json: Omit<DocumentClient.PutItemInput, "TableName">
171
+ }) {
152
172
  const params = {
153
173
  TableName: query.table,
154
174
  ...query.json,
@@ -189,7 +209,10 @@ class DynamoDBIntegration implements IntegrationBase {
189
209
  return new AWS.DynamoDB(this.config).describeTable(params).promise()
190
210
  }
191
211
 
192
- async get(query: { table: string; json: object }) {
212
+ async get(query: {
213
+ table: string
214
+ json: Omit<DocumentClient.GetItemInput, "TableName">
215
+ }) {
193
216
  const params = {
194
217
  TableName: query.table,
195
218
  ...query.json,
@@ -197,7 +220,10 @@ class DynamoDBIntegration implements IntegrationBase {
197
220
  return this.client.get(params).promise()
198
221
  }
199
222
 
200
- async update(query: { table: string; json: object }) {
223
+ async update(query: {
224
+ table: string
225
+ json: Omit<DocumentClient.UpdateItemInput, "TableName">
226
+ }) {
201
227
  const params = {
202
228
  TableName: query.table,
203
229
  ...query.json,
@@ -205,7 +231,10 @@ class DynamoDBIntegration implements IntegrationBase {
205
231
  return this.client.update(params).promise()
206
232
  }
207
233
 
208
- async delete(query: { table: string; json: object }) {
234
+ async delete(query: {
235
+ table: string
236
+ json: Omit<DocumentClient.DeleteItemInput, "TableName">
237
+ }) {
209
238
  const params = {
210
239
  TableName: query.table,
211
240
  ...query.json,
@@ -3,6 +3,8 @@ import {
3
3
  DatasourceFieldType,
4
4
  QueryType,
5
5
  IntegrationBase,
6
+ DatasourceFeature,
7
+ ConnectionInfo,
6
8
  } from "@budibase/types"
7
9
 
8
10
  import { Client, ClientOptions } from "@elastic/elasticsearch"
@@ -20,6 +22,7 @@ const SCHEMA: Integration = {
20
22
  "Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.",
21
23
  friendlyName: "ElasticSearch",
22
24
  type: "Non-relational",
25
+ features: [DatasourceFeature.CONNECTION_CHECKING],
23
26
  datasource: {
24
27
  url: {
25
28
  type: DatasourceFieldType.STRING,
@@ -95,7 +98,7 @@ const SCHEMA: Integration = {
95
98
 
96
99
  class ElasticSearchIntegration implements IntegrationBase {
97
100
  private config: ElasticsearchConfig
98
- private client: any
101
+ private client
99
102
 
100
103
  constructor(config: ElasticsearchConfig) {
101
104
  this.config = config
@@ -114,6 +117,18 @@ class ElasticSearchIntegration implements IntegrationBase {
114
117
  this.client = new Client(clientConfig)
115
118
  }
116
119
 
120
+ async testConnection(): Promise<ConnectionInfo> {
121
+ try {
122
+ await this.client.info()
123
+ return { connected: true }
124
+ } catch (e: any) {
125
+ return {
126
+ connected: false,
127
+ error: e.message as string,
128
+ }
129
+ }
130
+ }
131
+
117
132
  async create(query: { index: string; json: object }) {
118
133
  const { index, json } = query
119
134
 
@@ -3,6 +3,8 @@ import {
3
3
  Integration,
4
4
  QueryType,
5
5
  IntegrationBase,
6
+ DatasourceFeature,
7
+ ConnectionInfo,
6
8
  } from "@budibase/types"
7
9
  import { Firestore, WhereFilterOp } from "@google-cloud/firestore"
8
10
 
@@ -18,6 +20,7 @@ const SCHEMA: Integration = {
18
20
  type: "Non-relational",
19
21
  description:
20
22
  "Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.",
23
+ features: [DatasourceFeature.CONNECTION_CHECKING],
21
24
  datasource: {
22
25
  email: {
23
26
  type: DatasourceFieldType.STRING,
@@ -99,6 +102,18 @@ class FirebaseIntegration implements IntegrationBase {
99
102
  })
100
103
  }
101
104
 
105
+ async testConnection(): Promise<ConnectionInfo> {
106
+ try {
107
+ await this.client.listCollections()
108
+ return { connected: true }
109
+ } catch (e: any) {
110
+ return {
111
+ connected: false,
112
+ error: e.message as string,
113
+ }
114
+ }
115
+ }
116
+
102
117
  async create(query: { json: object; extra: { [key: string]: string } }) {
103
118
  try {
104
119
  const documentReference = this.client
@@ -1,4 +1,6 @@
1
1
  import {
2
+ ConnectionInfo,
3
+ DatasourceFeature,
2
4
  DatasourceFieldType,
3
5
  DatasourcePlus,
4
6
  FieldType,
@@ -15,7 +17,7 @@ import {
15
17
  } from "@budibase/types"
16
18
  import { OAuth2Client } from "google-auth-library"
17
19
  import { buildExternalTableId, finaliseExternalTables } from "./utils"
18
- import { GoogleSpreadsheet } from "google-spreadsheet"
20
+ import { GoogleSpreadsheet, GoogleSpreadsheetRow } from "google-spreadsheet"
19
21
  import fetch from "node-fetch"
20
22
  import { configs, HTTPError } from "@budibase/backend-core"
21
23
  import { dataFilters } from "@budibase/shared-core"
@@ -64,6 +66,7 @@ const SCHEMA: Integration = {
64
66
  "Create and collaborate on online spreadsheets in real-time and from any device. ",
65
67
  friendlyName: "Google Sheets",
66
68
  type: "Spreadsheet",
69
+ features: [DatasourceFeature.CONNECTION_CHECKING],
67
70
  datasource: {
68
71
  spreadsheetId: {
69
72
  display: "Google Sheet URL",
@@ -139,6 +142,19 @@ class GoogleSheetsIntegration implements DatasourcePlus {
139
142
  this.client = new GoogleSpreadsheet(spreadsheetId)
140
143
  }
141
144
 
145
+ async testConnection(): Promise<ConnectionInfo> {
146
+ try {
147
+ await this.connect()
148
+ await this.client.loadInfo()
149
+ return { connected: true }
150
+ } catch (e: any) {
151
+ return {
152
+ connected: false,
153
+ error: e.message as string,
154
+ }
155
+ }
156
+ }
157
+
142
158
  getBindingIdentifier() {
143
159
  return ""
144
160
  }
@@ -434,7 +450,20 @@ class GoogleSheetsIntegration implements DatasourcePlus {
434
450
  try {
435
451
  await this.connect()
436
452
  const sheet = this.client.sheetsByTitle[query.sheet]
437
- const rows = await sheet.getRows()
453
+ let rows: GoogleSpreadsheetRow[] = []
454
+ if (query.paginate) {
455
+ const limit = query.paginate.limit || 100
456
+ let page: number =
457
+ typeof query.paginate.page === "number"
458
+ ? query.paginate.page
459
+ : parseInt(query.paginate.page || "1")
460
+ rows = await sheet.getRows({
461
+ limit,
462
+ offset: (page - 1) * limit,
463
+ })
464
+ } else {
465
+ rows = await sheet.getRows()
466
+ }
438
467
  const filtered = dataFilters.runLuceneQuery(rows, query.filters)
439
468
  const headerValues = sheet.headerValues
440
469
  let response = []
@@ -20,7 +20,7 @@ import env from "../environment"
20
20
  import { cloneDeep } from "lodash"
21
21
  import sdk from "../sdk"
22
22
 
23
- const DEFINITIONS: { [key: string]: Integration } = {
23
+ const DEFINITIONS: Record<SourceName, Integration | undefined> = {
24
24
  [SourceName.POSTGRES]: postgres.schema,
25
25
  [SourceName.DYNAMODB]: dynamodb.schema,
26
26
  [SourceName.MONGODB]: mongodb.schema,
@@ -36,9 +36,10 @@ const DEFINITIONS: { [key: string]: Integration } = {
36
36
  [SourceName.GOOGLE_SHEETS]: googlesheets.schema,
37
37
  [SourceName.REDIS]: redis.schema,
38
38
  [SourceName.SNOWFLAKE]: snowflake.schema,
39
+ [SourceName.ORACLE]: undefined,
39
40
  }
40
41
 
41
- const INTEGRATIONS: { [key: string]: any } = {
42
+ const INTEGRATIONS: Record<SourceName, any> = {
42
43
  [SourceName.POSTGRES]: postgres.integration,
43
44
  [SourceName.DYNAMODB]: dynamodb.integration,
44
45
  [SourceName.MONGODB]: mongodb.integration,
@@ -55,6 +56,7 @@ const INTEGRATIONS: { [key: string]: any } = {
55
56
  [SourceName.REDIS]: redis.integration,
56
57
  [SourceName.FIRESTORE]: firebase.integration,
57
58
  [SourceName.SNOWFLAKE]: snowflake.integration,
59
+ [SourceName.ORACLE]: undefined,
58
60
  }
59
61
 
60
62
  // optionally add oracle integration if the oracle binary can be installed
@@ -67,10 +69,13 @@ if (
67
69
  INTEGRATIONS[SourceName.ORACLE] = oracle.integration
68
70
  }
69
71
 
70
- export async function getDefinition(source: SourceName): Promise<Integration> {
72
+ export async function getDefinition(
73
+ source: SourceName
74
+ ): Promise<Integration | undefined> {
71
75
  // check if its integrated, faster
72
- if (DEFINITIONS[source]) {
73
- return DEFINITIONS[source]
76
+ const definition = DEFINITIONS[source]
77
+ if (definition) {
78
+ return definition
74
79
  }
75
80
  const allDefinitions = await getDefinitions()
76
81
  return allDefinitions[source]
@@ -98,7 +103,7 @@ export async function getDefinitions() {
98
103
  }
99
104
  }
100
105
 
101
- export async function getIntegration(integration: string) {
106
+ export async function getIntegration(integration: SourceName) {
102
107
  if (INTEGRATIONS[integration]) {
103
108
  return INTEGRATIONS[integration]
104
109
  }
@@ -107,7 +112,7 @@ export async function getIntegration(integration: string) {
107
112
  for (let plugin of plugins) {
108
113
  if (plugin.name === integration) {
109
114
  // need to use commonJS require due to its dynamic runtime nature
110
- const retrieved: any = await getDatasourcePlugin(plugin)
115
+ const retrieved = await getDatasourcePlugin(plugin)
111
116
  if (retrieved.integration) {
112
117
  return retrieved.integration
113
118
  } else {
@@ -8,6 +8,8 @@ import {
8
8
  QueryType,
9
9
  SqlQuery,
10
10
  DatasourcePlus,
11
+ DatasourceFeature,
12
+ ConnectionInfo,
11
13
  } from "@budibase/types"
12
14
  import {
13
15
  getSqlQuery,
@@ -39,6 +41,7 @@ const SCHEMA: Integration = {
39
41
  "Microsoft SQL Server is a relational database management system developed by Microsoft. ",
40
42
  friendlyName: "MS SQL Server",
41
43
  type: "Relational",
44
+ features: [DatasourceFeature.CONNECTION_CHECKING],
42
45
  datasource: {
43
46
  user: {
44
47
  type: DatasourceFieldType.STRING,
@@ -121,6 +124,19 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
121
124
  }
122
125
  }
123
126
 
127
+ async testConnection() {
128
+ const response: ConnectionInfo = {
129
+ connected: false,
130
+ }
131
+ try {
132
+ await this.connect()
133
+ response.connected = true
134
+ } catch (e: any) {
135
+ response.error = e.message as string
136
+ }
137
+ return response
138
+ }
139
+
124
140
  getBindingIdentifier(): string {
125
141
  return `@p${this.index++}`
126
142
  }
@@ -3,6 +3,8 @@ import {
3
3
  DatasourceFieldType,
4
4
  QueryType,
5
5
  IntegrationBase,
6
+ DatasourceFeature,
7
+ ConnectionInfo,
6
8
  } from "@budibase/types"
7
9
  import {
8
10
  MongoClient,
@@ -38,6 +40,7 @@ const getSchema = () => {
38
40
  type: "Non-relational",
39
41
  description:
40
42
  "MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
43
+ features: [DatasourceFeature.CONNECTION_CHECKING],
41
44
  datasource: {
42
45
  connectionString: {
43
46
  type: DatasourceFieldType.STRING,
@@ -358,6 +361,19 @@ class MongoIntegration implements IntegrationBase {
358
361
  this.client = new MongoClient(config.connectionString, options)
359
362
  }
360
363
 
364
+ async testConnection() {
365
+ const response: ConnectionInfo = {
366
+ connected: false,
367
+ }
368
+ try {
369
+ await this.connect()
370
+ response.connected = true
371
+ } catch (e: any) {
372
+ response.error = e.message as string
373
+ }
374
+ return response
375
+ }
376
+
361
377
  async connect() {
362
378
  return this.client.connect()
363
379
  }
@@ -7,6 +7,8 @@ import {
7
7
  Table,
8
8
  TableSchema,
9
9
  DatasourcePlus,
10
+ DatasourceFeature,
11
+ ConnectionInfo,
10
12
  } from "@budibase/types"
11
13
  import {
12
14
  getSqlQuery,
@@ -20,18 +22,11 @@ import { NUMBER_REGEX } from "../utilities"
20
22
  import Sql from "./base/sql"
21
23
  import { MySQLColumn } from "./base/types"
22
24
 
23
- const mysql = require("mysql2/promise")
25
+ import mysql from "mysql2/promise"
24
26
 
25
- interface MySQLConfig {
26
- host: string
27
- port: number
28
- user: string
29
- password: string
27
+ interface MySQLConfig extends mysql.ConnectionOptions {
30
28
  database: string
31
- ssl?: { [key: string]: any }
32
29
  rejectUnauthorized: boolean
33
- typeCast: Function
34
- multipleStatements: boolean
35
30
  }
36
31
 
37
32
  const SCHEMA: Integration = {
@@ -41,6 +36,7 @@ const SCHEMA: Integration = {
41
36
  type: "Relational",
42
37
  description:
43
38
  "MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
39
+ features: [DatasourceFeature.CONNECTION_CHECKING],
44
40
  datasource: {
45
41
  host: {
46
42
  type: DatasourceFieldType.STRING,
@@ -92,8 +88,6 @@ const SCHEMA: Integration = {
92
88
  },
93
89
  }
94
90
 
95
- const TimezoneAwareDateTypes = ["timestamp"]
96
-
97
91
  function bindingTypeCoerce(bindings: any[]) {
98
92
  for (let i = 0; i < bindings.length; i++) {
99
93
  const binding = bindings[i]
@@ -120,7 +114,7 @@ function bindingTypeCoerce(bindings: any[]) {
120
114
 
121
115
  class MySQLIntegration extends Sql implements DatasourcePlus {
122
116
  private config: MySQLConfig
123
- private client: any
117
+ private client?: mysql.Connection
124
118
  public tables: Record<string, Table> = {}
125
119
  public schemaErrors: Record<string, string> = {}
126
120
 
@@ -134,7 +128,8 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
134
128
  if (
135
129
  config.rejectUnauthorized != null &&
136
130
  !config.rejectUnauthorized &&
137
- config.ssl
131
+ config.ssl &&
132
+ typeof config.ssl !== "string"
138
133
  ) {
139
134
  config.ssl.rejectUnauthorized = config.rejectUnauthorized
140
135
  }
@@ -160,6 +155,22 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
160
155
  }
161
156
  }
162
157
 
158
+ async testConnection() {
159
+ const response: ConnectionInfo = {
160
+ connected: false,
161
+ }
162
+ try {
163
+ const [result] = await this.internalQuery(
164
+ { sql: "SELECT 1+1 AS checkRes" },
165
+ { connect: true }
166
+ )
167
+ response.connected = result?.checkRes == 2
168
+ } catch (e: any) {
169
+ response.error = e.message as string
170
+ }
171
+ return response
172
+ }
173
+
163
174
  getBindingIdentifier(): string {
164
175
  return "?"
165
176
  }
@@ -173,7 +184,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
173
184
  }
174
185
 
175
186
  async disconnect() {
176
- await this.client.end()
187
+ await this.client!.end()
177
188
  }
178
189
 
179
190
  async internalQuery(
@@ -192,10 +203,10 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
192
203
  ? baseBindings
193
204
  : bindingTypeCoerce(baseBindings)
194
205
  // Node MySQL is callback based, so we must wrap our call in a promise
195
- const response = await this.client.query(query.sql, bindings)
206
+ const response = await this.client!.query(query.sql, bindings)
196
207
  return response[0]
197
208
  } finally {
198
- if (opts?.connect) {
209
+ if (opts?.connect && this.client) {
199
210
  await this.disconnect()
200
211
  }
201
212
  }
@@ -7,6 +7,8 @@ import {
7
7
  SqlQuery,
8
8
  Table,
9
9
  DatasourcePlus,
10
+ DatasourceFeature,
11
+ ConnectionInfo,
10
12
  } from "@budibase/types"
11
13
  import {
12
14
  buildExternalTableId,
@@ -24,12 +26,7 @@ import {
24
26
  ExecuteOptions,
25
27
  Result,
26
28
  } from "oracledb"
27
- import {
28
- OracleTable,
29
- OracleColumn,
30
- OracleColumnsResponse,
31
- OracleConstraint,
32
- } from "./base/types"
29
+ import { OracleTable, OracleColumn, OracleColumnsResponse } from "./base/types"
33
30
  let oracledb: any
34
31
  try {
35
32
  oracledb = require("oracledb")
@@ -53,6 +50,7 @@ const SCHEMA: Integration = {
53
50
  type: "Relational",
54
51
  description:
55
52
  "Oracle Database is an object-relational database management system developed by Oracle Corporation",
53
+ features: [DatasourceFeature.CONNECTION_CHECKING],
56
54
  datasource: {
57
55
  host: {
58
56
  type: DatasourceFieldType.STRING,
@@ -325,6 +323,30 @@ class OracleIntegration extends Sql implements DatasourcePlus {
325
323
  this.schemaErrors = final.errors
326
324
  }
327
325
 
326
+ async testConnection() {
327
+ const response: ConnectionInfo = {
328
+ connected: false,
329
+ }
330
+ let connection
331
+ try {
332
+ connection = await this.getConnection()
333
+ response.connected = true
334
+ } catch (err: any) {
335
+ response.connected = false
336
+ response.error = err.message
337
+ } finally {
338
+ if (connection) {
339
+ try {
340
+ await connection.close()
341
+ } catch (err: any) {
342
+ response.connected = false
343
+ response.error = err.message
344
+ }
345
+ }
346
+ }
347
+ return response
348
+ }
349
+
328
350
  private async internalQuery<T>(query: SqlQuery): Promise<Result<T>> {
329
351
  let connection
330
352
  try {
@@ -6,6 +6,8 @@ import {
6
6
  SqlQuery,
7
7
  Table,
8
8
  DatasourcePlus,
9
+ DatasourceFeature,
10
+ ConnectionInfo,
9
11
  } from "@budibase/types"
10
12
  import {
11
13
  getSqlQuery,
@@ -18,7 +20,7 @@ import Sql from "./base/sql"
18
20
  import { PostgresColumn } from "./base/types"
19
21
  import { escapeDangerousCharacters } from "../utilities"
20
22
 
21
- const { Client, types } = require("pg")
23
+ import { Client, types } from "pg"
22
24
 
23
25
  // Return "date" and "timestamp" types as plain strings.
24
26
  // This lets us reference the original stored timezone.
@@ -50,6 +52,7 @@ const SCHEMA: Integration = {
50
52
  type: "Relational",
51
53
  description:
52
54
  "PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.",
55
+ features: [DatasourceFeature.CONNECTION_CHECKING],
53
56
  datasource: {
54
57
  host: {
55
58
  type: DatasourceFieldType.STRING,
@@ -114,7 +117,7 @@ const SCHEMA: Integration = {
114
117
  }
115
118
 
116
119
  class PostgresIntegration extends Sql implements DatasourcePlus {
117
- private readonly client: any
120
+ private readonly client: Client
118
121
  private readonly config: PostgresConfig
119
122
  private index: number = 1
120
123
  private open: boolean
@@ -150,6 +153,21 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
150
153
  this.open = false
151
154
  }
152
155
 
156
+ async testConnection() {
157
+ const response: ConnectionInfo = {
158
+ connected: false,
159
+ }
160
+ try {
161
+ await this.openConnection()
162
+ response.connected = true
163
+ } catch (e: any) {
164
+ response.error = e.message as string
165
+ } finally {
166
+ await this.closeConnection()
167
+ }
168
+ return response
169
+ }
170
+
153
171
  getBindingIdentifier(): string {
154
172
  return `$${this.index++}`
155
173
  }
@@ -163,7 +181,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
163
181
  if (!this.config.schema) {
164
182
  this.config.schema = "public"
165
183
  }
166
- this.client.query(`SET search_path TO ${this.config.schema}`)
184
+ await this.client.query(`SET search_path TO ${this.config.schema}`)
167
185
  this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'`
168
186
  this.open = true
169
187
  }
@@ -1,4 +1,10 @@
1
- import { DatasourceFieldType, Integration, QueryType } from "@budibase/types"
1
+ import {
2
+ ConnectionInfo,
3
+ DatasourceFeature,
4
+ DatasourceFieldType,
5
+ Integration,
6
+ QueryType,
7
+ } from "@budibase/types"
2
8
  import Redis from "ioredis"
3
9
 
4
10
  interface RedisConfig {
@@ -11,9 +17,11 @@ interface RedisConfig {
11
17
 
12
18
  const SCHEMA: Integration = {
13
19
  docs: "https://redis.io/docs/",
14
- description: "",
20
+ description:
21
+ "Redis is a caching tool, providing powerful key-value store capabilities.",
15
22
  friendlyName: "Redis",
16
23
  type: "Non-relational",
24
+ features: [DatasourceFeature.CONNECTION_CHECKING],
17
25
  datasource: {
18
26
  host: {
19
27
  type: "string",
@@ -86,7 +94,7 @@ const SCHEMA: Integration = {
86
94
 
87
95
  class RedisIntegration {
88
96
  private readonly config: RedisConfig
89
- private client: any
97
+ private client
90
98
 
91
99
  constructor(config: RedisConfig) {
92
100
  this.config = config
@@ -99,6 +107,21 @@ class RedisIntegration {
99
107
  })
100
108
  }
101
109
 
110
+ async testConnection() {
111
+ const response: ConnectionInfo = {
112
+ connected: false,
113
+ }
114
+ try {
115
+ await this.client.ping()
116
+ response.connected = true
117
+ } catch (e: any) {
118
+ response.error = e.message as string
119
+ } finally {
120
+ await this.disconnect()
121
+ }
122
+ return response
123
+ }
124
+
102
125
  async disconnect() {
103
126
  return this.client.quit()
104
127
  }