Merge pull request #10621 from Budibase/feature/datasource-conns
Verify datasource connections
This commit is contained in:
commit
264ebf1d2b
|
@ -12,7 +12,7 @@ import {
|
|||
isDocument,
|
||||
} from "@budibase/types"
|
||||
import { getCouchInfo } from "./connections"
|
||||
import { directCouchCall } from "./utils"
|
||||
import { directCouchUrlCall } from "./utils"
|
||||
import { getPouchDB } from "./pouchDB"
|
||||
import { WriteStream, ReadStream } from "fs"
|
||||
import { newid } from "../../docIds/newid"
|
||||
|
@ -46,6 +46,8 @@ export class DatabaseImpl implements Database {
|
|||
private readonly instanceNano?: Nano.ServerScope
|
||||
private readonly pouchOpts: DatabaseOpts
|
||||
|
||||
private readonly couchInfo = getCouchInfo()
|
||||
|
||||
constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
|
||||
if (dbName == null) {
|
||||
throw new Error("Database name cannot be undefined.")
|
||||
|
@ -53,8 +55,8 @@ export class DatabaseImpl implements Database {
|
|||
this.name = dbName
|
||||
this.pouchOpts = opts || {}
|
||||
if (connection) {
|
||||
const couchInfo = getCouchInfo(connection)
|
||||
this.instanceNano = buildNano(couchInfo)
|
||||
this.couchInfo = getCouchInfo(connection)
|
||||
this.instanceNano = buildNano(this.couchInfo)
|
||||
}
|
||||
if (!DatabaseImpl.nano) {
|
||||
DatabaseImpl.init()
|
||||
|
@ -67,7 +69,11 @@ export class DatabaseImpl implements Database {
|
|||
}
|
||||
|
||||
async exists() {
|
||||
let response = await directCouchCall(`/${this.name}`, "HEAD")
|
||||
const response = await directCouchUrlCall({
|
||||
url: `${this.couchInfo.url}/${this.name}`,
|
||||
method: "HEAD",
|
||||
cookie: this.couchInfo.cookie,
|
||||
})
|
||||
return response.status === 200
|
||||
}
|
||||
|
||||
|
|
|
@ -4,21 +4,21 @@ export const getCouchInfo = (connection?: string) => {
|
|||
const urlInfo = getUrlInfo(connection)
|
||||
let username
|
||||
let password
|
||||
if (env.COUCH_DB_USERNAME) {
|
||||
// set from env
|
||||
username = env.COUCH_DB_USERNAME
|
||||
} else if (urlInfo.auth.username) {
|
||||
if (urlInfo.auth?.username) {
|
||||
// set from url
|
||||
username = urlInfo.auth.username
|
||||
} else if (env.COUCH_DB_USERNAME) {
|
||||
// set from env
|
||||
username = env.COUCH_DB_USERNAME
|
||||
} else if (!env.isTest()) {
|
||||
throw new Error("CouchDB username not set")
|
||||
}
|
||||
if (env.COUCH_DB_PASSWORD) {
|
||||
// set from env
|
||||
password = env.COUCH_DB_PASSWORD
|
||||
} else if (urlInfo.auth.password) {
|
||||
if (urlInfo.auth?.password) {
|
||||
// set from url
|
||||
password = urlInfo.auth.password
|
||||
} else if (env.COUCH_DB_PASSWORD) {
|
||||
// set from env
|
||||
password = env.COUCH_DB_PASSWORD
|
||||
} else if (!env.isTest()) {
|
||||
throw new Error("CouchDB password not set")
|
||||
}
|
||||
|
|
|
@ -9,6 +9,20 @@ export async function directCouchCall(
|
|||
) {
|
||||
let { url, cookie } = getCouchInfo()
|
||||
const couchUrl = `${url}/${path}`
|
||||
return await directCouchUrlCall({ url: couchUrl, cookie, method, body })
|
||||
}
|
||||
|
||||
export async function directCouchUrlCall({
|
||||
url,
|
||||
cookie,
|
||||
method,
|
||||
body,
|
||||
}: {
|
||||
url: string
|
||||
cookie: string
|
||||
method: string
|
||||
body?: any
|
||||
}) {
|
||||
const params: any = {
|
||||
method: method,
|
||||
headers: {
|
||||
|
@ -19,7 +33,7 @@ export async function directCouchCall(
|
|||
params.body = JSON.stringify(body)
|
||||
params.headers["Content-Type"] = "application/json"
|
||||
}
|
||||
return await fetch(checkSlashesInUrl(encodeURI(couchUrl)), params)
|
||||
return await fetch(checkSlashesInUrl(encodeURI(url)), params)
|
||||
}
|
||||
|
||||
export async function directCouchQuery(
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
"mysql2": "2.3.3",
|
||||
"node-fetch": "2.6.7",
|
||||
"open": "8.4.0",
|
||||
"pg": "8.5.1",
|
||||
"pg": "8.10.0",
|
||||
"posthog-node": "1.3.0",
|
||||
"pouchdb": "7.3.0",
|
||||
"pouchdb-adapter-memory": "7.2.2",
|
||||
|
@ -141,6 +141,7 @@
|
|||
"@types/node": "14.18.20",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/oracledb": "5.2.2",
|
||||
"@types/pg": "8.6.6",
|
||||
"@types/pouchdb": "6.4.0",
|
||||
"@types/redis": "4.0.11",
|
||||
"@types/server-destroy": "1.0.1",
|
||||
|
|
|
@ -18,11 +18,71 @@ import {
|
|||
Row,
|
||||
CreateDatasourceResponse,
|
||||
UpdateDatasourceResponse,
|
||||
UpdateDatasourceRequest,
|
||||
CreateDatasourceRequest,
|
||||
VerifyDatasourceRequest,
|
||||
VerifyDatasourceResponse,
|
||||
IntegrationBase,
|
||||
DatasourcePlus,
|
||||
} from "@budibase/types"
|
||||
import sdk from "../../sdk"
|
||||
|
||||
function getErrorTables(errors: any, errorType: string) {
|
||||
return Object.entries(errors)
|
||||
.filter(entry => entry[1] === errorType)
|
||||
.map(([name]) => name)
|
||||
}
|
||||
|
||||
function updateError(error: any, newError: any, tables: string[]) {
|
||||
if (!error) {
|
||||
error = ""
|
||||
}
|
||||
if (error.length > 0) {
|
||||
error += "\n"
|
||||
}
|
||||
error += `${newError} ${tables.join(", ")}`
|
||||
return error
|
||||
}
|
||||
|
||||
async function getConnector(
|
||||
datasource: Datasource
|
||||
): Promise<IntegrationBase | DatasourcePlus> {
|
||||
const Connector = await getIntegration(datasource.source)
|
||||
// can't enrich if it doesn't have an ID yet
|
||||
if (datasource._id) {
|
||||
datasource = await sdk.datasources.enrich(datasource)
|
||||
}
|
||||
// Connect to the DB and build the schema
|
||||
return new Connector(datasource.config)
|
||||
}
|
||||
|
||||
async function buildSchemaHelper(datasource: Datasource) {
|
||||
const connector = (await getConnector(datasource)) as DatasourcePlus
|
||||
await connector.buildSchema(datasource._id!, datasource.entities!)
|
||||
|
||||
const errors = connector.schemaErrors
|
||||
let error = null
|
||||
if (errors && Object.keys(errors).length > 0) {
|
||||
const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
|
||||
const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
|
||||
if (noKey.length) {
|
||||
error = updateError(
|
||||
error,
|
||||
"No primary key constraint found for the following:",
|
||||
noKey
|
||||
)
|
||||
}
|
||||
if (invalidCol.length) {
|
||||
const invalidCols = Object.values(InvalidColumns).join(", ")
|
||||
error = updateError(
|
||||
error,
|
||||
`Cannot use columns ${invalidCols} found in following:`,
|
||||
invalidCol
|
||||
)
|
||||
}
|
||||
}
|
||||
return { tables: connector.tables, error }
|
||||
}
|
||||
|
||||
export async function fetch(ctx: UserCtx) {
|
||||
// Get internal tables
|
||||
const db = context.getAppDB()
|
||||
|
@ -66,6 +126,33 @@ export async function fetch(ctx: UserCtx) {
|
|||
ctx.body = [bbInternalDb, ...datasources]
|
||||
}
|
||||
|
||||
export async function verify(
|
||||
ctx: UserCtx<VerifyDatasourceRequest, VerifyDatasourceResponse>
|
||||
) {
|
||||
const { datasource } = ctx.request.body
|
||||
let existingDatasource: undefined | Datasource
|
||||
if (datasource._id) {
|
||||
existingDatasource = await sdk.datasources.get(datasource._id)
|
||||
}
|
||||
let enrichedDatasource = datasource
|
||||
if (existingDatasource) {
|
||||
enrichedDatasource = sdk.datasources.mergeConfigs(
|
||||
datasource,
|
||||
existingDatasource
|
||||
)
|
||||
}
|
||||
const connector = await getConnector(enrichedDatasource)
|
||||
if (!connector.testConnection) {
|
||||
ctx.throw(400, "Connection information verification not supported")
|
||||
}
|
||||
const response = await connector.testConnection()
|
||||
|
||||
ctx.body = {
|
||||
connected: response.connected,
|
||||
error: response.error,
|
||||
}
|
||||
}
|
||||
|
||||
export async function buildSchemaFromDb(ctx: UserCtx) {
|
||||
const db = context.getAppDB()
|
||||
const datasource = await sdk.datasources.get(ctx.params.datasourceId)
|
||||
|
@ -311,51 +398,3 @@ export async function query(ctx: UserCtx) {
|
|||
ctx.throw(400, err)
|
||||
}
|
||||
}
|
||||
|
||||
function getErrorTables(errors: any, errorType: string) {
|
||||
return Object.entries(errors)
|
||||
.filter(entry => entry[1] === errorType)
|
||||
.map(([name]) => name)
|
||||
}
|
||||
|
||||
function updateError(error: any, newError: any, tables: string[]) {
|
||||
if (!error) {
|
||||
error = ""
|
||||
}
|
||||
if (error.length > 0) {
|
||||
error += "\n"
|
||||
}
|
||||
error += `${newError} ${tables.join(", ")}`
|
||||
return error
|
||||
}
|
||||
|
||||
async function buildSchemaHelper(datasource: Datasource) {
|
||||
const Connector = await getIntegration(datasource.source)
|
||||
datasource = await sdk.datasources.enrich(datasource)
|
||||
// Connect to the DB and build the schema
|
||||
const connector = new Connector(datasource.config)
|
||||
await connector.buildSchema(datasource._id, datasource.entities)
|
||||
|
||||
const errors = connector.schemaErrors
|
||||
let error = null
|
||||
if (errors && Object.keys(errors).length > 0) {
|
||||
const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
|
||||
const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
|
||||
if (noKey.length) {
|
||||
error = updateError(
|
||||
error,
|
||||
"No primary key constraint found for the following:",
|
||||
noKey
|
||||
)
|
||||
}
|
||||
if (invalidCol.length) {
|
||||
const invalidCols = Object.values(InvalidColumns).join(", ")
|
||||
error = updateError(
|
||||
error,
|
||||
`Cannot use columns ${invalidCols} found in following:`,
|
||||
invalidCol
|
||||
)
|
||||
}
|
||||
}
|
||||
return { tables: connector.tables, error }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getDefinitions } from "../../integrations"
|
||||
import { getDefinition, getDefinitions } from "../../integrations"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
export async function fetch(ctx: BBContext) {
|
||||
|
@ -7,7 +7,7 @@ export async function fetch(ctx: BBContext) {
|
|||
}
|
||||
|
||||
export async function find(ctx: BBContext) {
|
||||
const defs = await getDefinitions()
|
||||
const def = await getDefinition(ctx.params.type)
|
||||
ctx.body = def
|
||||
ctx.status = 200
|
||||
ctx.body = defs[ctx.params.type]
|
||||
}
|
||||
|
|
|
@ -15,6 +15,11 @@ router
|
|||
authorized(permissions.BUILDER),
|
||||
datasourceController.fetch
|
||||
)
|
||||
.post(
|
||||
"/api/datasources/verify",
|
||||
authorized(permissions.BUILDER),
|
||||
datasourceController.verify
|
||||
)
|
||||
.get(
|
||||
"/api/datasources/:datasourceId",
|
||||
authorized(
|
||||
|
|
|
@ -140,7 +140,7 @@ export function init(endpoint: string) {
|
|||
docClient = new AWS.DynamoDB.DocumentClient(docClientParams)
|
||||
}
|
||||
|
||||
if (!env.isProd()) {
|
||||
if (!env.isProd() && !env.isJest()) {
|
||||
env._set("AWS_ACCESS_KEY_ID", "KEY_ID")
|
||||
env._set("AWS_SECRET_ACCESS_KEY", "SECRET_KEY")
|
||||
init("http://localhost:8333")
|
||||
|
|
|
@ -19,7 +19,6 @@ import _ from "lodash"
|
|||
import { generator } from "@budibase/backend-core/tests"
|
||||
import { utils } from "@budibase/backend-core"
|
||||
import { GenericContainer } from "testcontainers"
|
||||
import { generateRowIdField } from "../integrations/utils"
|
||||
|
||||
const config = setup.getConfig()!
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import {
|
||||
Integration,
|
||||
ConnectionInfo,
|
||||
DatasourceFeature,
|
||||
DatasourceFieldType,
|
||||
QueryType,
|
||||
Integration,
|
||||
IntegrationBase,
|
||||
QueryType,
|
||||
} from "@budibase/types"
|
||||
|
||||
const Airtable = require("airtable")
|
||||
import Airtable from "airtable"
|
||||
|
||||
interface AirtableConfig {
|
||||
apiKey: string
|
||||
|
@ -18,6 +20,7 @@ const SCHEMA: Integration = {
|
|||
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.",
|
||||
friendlyName: "Airtable",
|
||||
type: "Spreadsheet",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
apiKey: {
|
||||
type: DatasourceFieldType.PASSWORD,
|
||||
|
@ -81,13 +84,37 @@ const SCHEMA: Integration = {
|
|||
|
||||
class AirtableIntegration implements IntegrationBase {
|
||||
private config: AirtableConfig
|
||||
private client: any
|
||||
private client
|
||||
|
||||
constructor(config: AirtableConfig) {
|
||||
this.config = config
|
||||
this.client = new Airtable(config).base(config.base)
|
||||
}
|
||||
|
||||
async testConnection(): Promise<ConnectionInfo> {
|
||||
const mockTable = Date.now().toString()
|
||||
try {
|
||||
await this.client.makeRequest({
|
||||
path: `/${mockTable}`,
|
||||
})
|
||||
|
||||
return { connected: true }
|
||||
} catch (e: any) {
|
||||
if (
|
||||
e.message ===
|
||||
`Could not find table ${mockTable} in application ${this.config.base}`
|
||||
) {
|
||||
// The request managed to check the application, so the credentials are valid
|
||||
return { connected: true }
|
||||
}
|
||||
|
||||
return {
|
||||
connected: false,
|
||||
error: e.message as string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async create(query: { table: any; json: any }) {
|
||||
const { table, json } = query
|
||||
|
||||
|
|
|
@ -3,9 +3,11 @@ import {
|
|||
DatasourceFieldType,
|
||||
QueryType,
|
||||
IntegrationBase,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
|
||||
const { Database, aql } = require("arangojs")
|
||||
import { Database, aql } from "arangojs"
|
||||
|
||||
interface ArangodbConfig {
|
||||
url: string
|
||||
|
@ -21,6 +23,7 @@ const SCHEMA: Integration = {
|
|||
type: "Non-relational",
|
||||
description:
|
||||
"ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
url: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -58,7 +61,7 @@ const SCHEMA: Integration = {
|
|||
|
||||
class ArangoDBIntegration implements IntegrationBase {
|
||||
private config: ArangodbConfig
|
||||
private client: any
|
||||
private client
|
||||
|
||||
constructor(config: ArangodbConfig) {
|
||||
const newConfig = {
|
||||
|
@ -74,6 +77,19 @@ class ArangoDBIntegration implements IntegrationBase {
|
|||
this.client = new Database(newConfig)
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
try {
|
||||
await this.client.get()
|
||||
response.connected = true
|
||||
} catch (e: any) {
|
||||
response.error = e.message as string
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async read(query: { sql: any }) {
|
||||
try {
|
||||
const result = await this.client.query(query.sql)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import {
|
||||
ConnectionInfo,
|
||||
DatasourceFeature,
|
||||
DatasourceFieldType,
|
||||
Document,
|
||||
Integration,
|
||||
|
@ -18,6 +20,7 @@ const SCHEMA: Integration = {
|
|||
type: "Non-relational",
|
||||
description:
|
||||
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
url: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -61,21 +64,32 @@ const SCHEMA: Integration = {
|
|||
}
|
||||
|
||||
class CouchDBIntegration implements IntegrationBase {
|
||||
private config: CouchDBConfig
|
||||
private readonly client: any
|
||||
private readonly client: dbCore.DatabaseImpl
|
||||
|
||||
constructor(config: CouchDBConfig) {
|
||||
this.config = config
|
||||
this.client = dbCore.DatabaseWithConnection(config.database, config.url)
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
try {
|
||||
const result = await this.query("exists", "validation error", {})
|
||||
response.connected = result === true
|
||||
} catch (e: any) {
|
||||
response.error = e.message as string
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async query(
|
||||
command: string,
|
||||
errorMsg: string,
|
||||
query: { json?: object; id?: string }
|
||||
) {
|
||||
try {
|
||||
return await this.client[command](query.id || query.json)
|
||||
return await (this.client as any)[command](query.id || query.json)
|
||||
} catch (err) {
|
||||
console.error(errorMsg, err)
|
||||
throw err
|
||||
|
|
|
@ -3,10 +3,13 @@ import {
|
|||
DatasourceFieldType,
|
||||
QueryType,
|
||||
IntegrationBase,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
|
||||
import AWS from "aws-sdk"
|
||||
import { AWS_REGION } from "../db/dynamoClient"
|
||||
import { DocumentClient } from "aws-sdk/clients/dynamodb"
|
||||
|
||||
interface DynamoDBConfig {
|
||||
region: string
|
||||
|
@ -22,6 +25,7 @@ const SCHEMA: Integration = {
|
|||
"Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.",
|
||||
friendlyName: "DynamoDB",
|
||||
type: "Non-relational",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
region: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -128,7 +132,7 @@ const SCHEMA: Integration = {
|
|||
|
||||
class DynamoDBIntegration implements IntegrationBase {
|
||||
private config: DynamoDBConfig
|
||||
private client: any
|
||||
private client
|
||||
|
||||
constructor(config: DynamoDBConfig) {
|
||||
this.config = config
|
||||
|
@ -148,7 +152,23 @@ class DynamoDBIntegration implements IntegrationBase {
|
|||
this.client = new AWS.DynamoDB.DocumentClient(this.config)
|
||||
}
|
||||
|
||||
async create(query: { table: string; json: object }) {
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
try {
|
||||
const scanRes = await new AWS.DynamoDB(this.config).listTables().promise()
|
||||
response.connected = !!scanRes.$response
|
||||
} catch (e: any) {
|
||||
response.error = e.message as string
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async create(query: {
|
||||
table: string
|
||||
json: Omit<DocumentClient.PutItemInput, "TableName">
|
||||
}) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
|
@ -189,7 +209,10 @@ class DynamoDBIntegration implements IntegrationBase {
|
|||
return new AWS.DynamoDB(this.config).describeTable(params).promise()
|
||||
}
|
||||
|
||||
async get(query: { table: string; json: object }) {
|
||||
async get(query: {
|
||||
table: string
|
||||
json: Omit<DocumentClient.GetItemInput, "TableName">
|
||||
}) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
|
@ -197,7 +220,10 @@ class DynamoDBIntegration implements IntegrationBase {
|
|||
return this.client.get(params).promise()
|
||||
}
|
||||
|
||||
async update(query: { table: string; json: object }) {
|
||||
async update(query: {
|
||||
table: string
|
||||
json: Omit<DocumentClient.UpdateItemInput, "TableName">
|
||||
}) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
|
@ -205,7 +231,10 @@ class DynamoDBIntegration implements IntegrationBase {
|
|||
return this.client.update(params).promise()
|
||||
}
|
||||
|
||||
async delete(query: { table: string; json: object }) {
|
||||
async delete(query: {
|
||||
table: string
|
||||
json: Omit<DocumentClient.DeleteItemInput, "TableName">
|
||||
}) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
|
|
|
@ -3,6 +3,8 @@ import {
|
|||
DatasourceFieldType,
|
||||
QueryType,
|
||||
IntegrationBase,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
|
||||
import { Client, ClientOptions } from "@elastic/elasticsearch"
|
||||
|
@ -20,6 +22,7 @@ const SCHEMA: Integration = {
|
|||
"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.",
|
||||
friendlyName: "ElasticSearch",
|
||||
type: "Non-relational",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
url: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -95,7 +98,7 @@ const SCHEMA: Integration = {
|
|||
|
||||
class ElasticSearchIntegration implements IntegrationBase {
|
||||
private config: ElasticsearchConfig
|
||||
private client: any
|
||||
private client
|
||||
|
||||
constructor(config: ElasticsearchConfig) {
|
||||
this.config = config
|
||||
|
@ -114,6 +117,18 @@ class ElasticSearchIntegration implements IntegrationBase {
|
|||
this.client = new Client(clientConfig)
|
||||
}
|
||||
|
||||
async testConnection(): Promise<ConnectionInfo> {
|
||||
try {
|
||||
await this.client.info()
|
||||
return { connected: true }
|
||||
} catch (e: any) {
|
||||
return {
|
||||
connected: false,
|
||||
error: e.message as string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async create(query: { index: string; json: object }) {
|
||||
const { index, json } = query
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ import {
|
|||
Integration,
|
||||
QueryType,
|
||||
IntegrationBase,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
import { Firestore, WhereFilterOp } from "@google-cloud/firestore"
|
||||
|
||||
|
@ -18,6 +20,7 @@ const SCHEMA: Integration = {
|
|||
type: "Non-relational",
|
||||
description:
|
||||
"Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
email: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -99,6 +102,18 @@ class FirebaseIntegration implements IntegrationBase {
|
|||
})
|
||||
}
|
||||
|
||||
async testConnection(): Promise<ConnectionInfo> {
|
||||
try {
|
||||
await this.client.listCollections()
|
||||
return { connected: true }
|
||||
} catch (e: any) {
|
||||
return {
|
||||
connected: false,
|
||||
error: e.message as string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async create(query: { json: object; extra: { [key: string]: string } }) {
|
||||
try {
|
||||
const documentReference = this.client
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import {
|
||||
ConnectionInfo,
|
||||
DatasourceFeature,
|
||||
DatasourceFieldType,
|
||||
DatasourcePlus,
|
||||
FieldType,
|
||||
|
@ -64,6 +66,7 @@ const SCHEMA: Integration = {
|
|||
"Create and collaborate on online spreadsheets in real-time and from any device. ",
|
||||
friendlyName: "Google Sheets",
|
||||
type: "Spreadsheet",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
spreadsheetId: {
|
||||
display: "Google Sheet URL",
|
||||
|
@ -139,6 +142,19 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|||
this.client = new GoogleSpreadsheet(spreadsheetId)
|
||||
}
|
||||
|
||||
async testConnection(): Promise<ConnectionInfo> {
|
||||
try {
|
||||
await this.connect()
|
||||
await this.client.loadInfo()
|
||||
return { connected: true }
|
||||
} catch (e: any) {
|
||||
return {
|
||||
connected: false,
|
||||
error: e.message as string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getBindingIdentifier() {
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import env from "../environment"
|
|||
import { cloneDeep } from "lodash"
|
||||
import sdk from "../sdk"
|
||||
|
||||
const DEFINITIONS: { [key: string]: Integration } = {
|
||||
const DEFINITIONS: Record<SourceName, Integration | undefined> = {
|
||||
[SourceName.POSTGRES]: postgres.schema,
|
||||
[SourceName.DYNAMODB]: dynamodb.schema,
|
||||
[SourceName.MONGODB]: mongodb.schema,
|
||||
|
@ -36,9 +36,10 @@ const DEFINITIONS: { [key: string]: Integration } = {
|
|||
[SourceName.GOOGLE_SHEETS]: googlesheets.schema,
|
||||
[SourceName.REDIS]: redis.schema,
|
||||
[SourceName.SNOWFLAKE]: snowflake.schema,
|
||||
[SourceName.ORACLE]: undefined,
|
||||
}
|
||||
|
||||
const INTEGRATIONS: { [key: string]: any } = {
|
||||
const INTEGRATIONS: Record<SourceName, any> = {
|
||||
[SourceName.POSTGRES]: postgres.integration,
|
||||
[SourceName.DYNAMODB]: dynamodb.integration,
|
||||
[SourceName.MONGODB]: mongodb.integration,
|
||||
|
@ -55,6 +56,7 @@ const INTEGRATIONS: { [key: string]: any } = {
|
|||
[SourceName.REDIS]: redis.integration,
|
||||
[SourceName.FIRESTORE]: firebase.integration,
|
||||
[SourceName.SNOWFLAKE]: snowflake.integration,
|
||||
[SourceName.ORACLE]: undefined,
|
||||
}
|
||||
|
||||
// optionally add oracle integration if the oracle binary can be installed
|
||||
|
@ -67,10 +69,13 @@ if (
|
|||
INTEGRATIONS[SourceName.ORACLE] = oracle.integration
|
||||
}
|
||||
|
||||
export async function getDefinition(source: SourceName): Promise<Integration> {
|
||||
export async function getDefinition(
|
||||
source: SourceName
|
||||
): Promise<Integration | undefined> {
|
||||
// check if its integrated, faster
|
||||
if (DEFINITIONS[source]) {
|
||||
return DEFINITIONS[source]
|
||||
const definition = DEFINITIONS[source]
|
||||
if (definition) {
|
||||
return definition
|
||||
}
|
||||
const allDefinitions = await getDefinitions()
|
||||
return allDefinitions[source]
|
||||
|
@ -98,7 +103,7 @@ export async function getDefinitions() {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getIntegration(integration: string) {
|
||||
export async function getIntegration(integration: SourceName) {
|
||||
if (INTEGRATIONS[integration]) {
|
||||
return INTEGRATIONS[integration]
|
||||
}
|
||||
|
@ -107,7 +112,7 @@ export async function getIntegration(integration: string) {
|
|||
for (let plugin of plugins) {
|
||||
if (plugin.name === integration) {
|
||||
// need to use commonJS require due to its dynamic runtime nature
|
||||
const retrieved: any = await getDatasourcePlugin(plugin)
|
||||
const retrieved = await getDatasourcePlugin(plugin)
|
||||
if (retrieved.integration) {
|
||||
return retrieved.integration
|
||||
} else {
|
||||
|
|
|
@ -8,6 +8,8 @@ import {
|
|||
QueryType,
|
||||
SqlQuery,
|
||||
DatasourcePlus,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
import {
|
||||
getSqlQuery,
|
||||
|
@ -39,6 +41,7 @@ const SCHEMA: Integration = {
|
|||
"Microsoft SQL Server is a relational database management system developed by Microsoft. ",
|
||||
friendlyName: "MS SQL Server",
|
||||
type: "Relational",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
user: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -121,6 +124,19 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
|||
}
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
try {
|
||||
await this.connect()
|
||||
response.connected = true
|
||||
} catch (e: any) {
|
||||
response.error = e.message as string
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
getBindingIdentifier(): string {
|
||||
return `@p${this.index++}`
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import {
|
|||
DatasourceFieldType,
|
||||
QueryType,
|
||||
IntegrationBase,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
import {
|
||||
MongoClient,
|
||||
|
@ -38,6 +40,7 @@ const getSchema = () => {
|
|||
type: "Non-relational",
|
||||
description:
|
||||
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
connectionString: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -358,6 +361,19 @@ class MongoIntegration implements IntegrationBase {
|
|||
this.client = new MongoClient(config.connectionString, options)
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
try {
|
||||
await this.connect()
|
||||
response.connected = true
|
||||
} catch (e: any) {
|
||||
response.error = e.message as string
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async connect() {
|
||||
return this.client.connect()
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
Table,
|
||||
TableSchema,
|
||||
DatasourcePlus,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
import {
|
||||
getSqlQuery,
|
||||
|
@ -20,18 +22,11 @@ import { NUMBER_REGEX } from "../utilities"
|
|||
import Sql from "./base/sql"
|
||||
import { MySQLColumn } from "./base/types"
|
||||
|
||||
const mysql = require("mysql2/promise")
|
||||
import mysql from "mysql2/promise"
|
||||
|
||||
interface MySQLConfig {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
interface MySQLConfig extends mysql.ConnectionOptions {
|
||||
database: string
|
||||
ssl?: { [key: string]: any }
|
||||
rejectUnauthorized: boolean
|
||||
typeCast: Function
|
||||
multipleStatements: boolean
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
|
@ -41,6 +36,7 @@ const SCHEMA: Integration = {
|
|||
type: "Relational",
|
||||
description:
|
||||
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
host: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -92,8 +88,6 @@ const SCHEMA: Integration = {
|
|||
},
|
||||
}
|
||||
|
||||
const TimezoneAwareDateTypes = ["timestamp"]
|
||||
|
||||
function bindingTypeCoerce(bindings: any[]) {
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i]
|
||||
|
@ -120,7 +114,7 @@ function bindingTypeCoerce(bindings: any[]) {
|
|||
|
||||
class MySQLIntegration extends Sql implements DatasourcePlus {
|
||||
private config: MySQLConfig
|
||||
private client: any
|
||||
private client?: mysql.Connection
|
||||
public tables: Record<string, Table> = {}
|
||||
public schemaErrors: Record<string, string> = {}
|
||||
|
||||
|
@ -134,7 +128,8 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
|||
if (
|
||||
config.rejectUnauthorized != null &&
|
||||
!config.rejectUnauthorized &&
|
||||
config.ssl
|
||||
config.ssl &&
|
||||
typeof config.ssl !== "string"
|
||||
) {
|
||||
config.ssl.rejectUnauthorized = config.rejectUnauthorized
|
||||
}
|
||||
|
@ -160,6 +155,22 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
|||
}
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
try {
|
||||
const [result] = await this.internalQuery(
|
||||
{ sql: "SELECT 1+1 AS checkRes" },
|
||||
{ connect: true }
|
||||
)
|
||||
response.connected = result?.checkRes == 2
|
||||
} catch (e: any) {
|
||||
response.error = e.message as string
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
getBindingIdentifier(): string {
|
||||
return "?"
|
||||
}
|
||||
|
@ -173,7 +184,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
|||
}
|
||||
|
||||
async disconnect() {
|
||||
await this.client.end()
|
||||
await this.client!.end()
|
||||
}
|
||||
|
||||
async internalQuery(
|
||||
|
@ -192,10 +203,10 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
|||
? baseBindings
|
||||
: bindingTypeCoerce(baseBindings)
|
||||
// Node MySQL is callback based, so we must wrap our call in a promise
|
||||
const response = await this.client.query(query.sql, bindings)
|
||||
const response = await this.client!.query(query.sql, bindings)
|
||||
return response[0]
|
||||
} finally {
|
||||
if (opts?.connect) {
|
||||
if (opts?.connect && this.client) {
|
||||
await this.disconnect()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
SqlQuery,
|
||||
Table,
|
||||
DatasourcePlus,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
import {
|
||||
buildExternalTableId,
|
||||
|
@ -24,12 +26,7 @@ import {
|
|||
ExecuteOptions,
|
||||
Result,
|
||||
} from "oracledb"
|
||||
import {
|
||||
OracleTable,
|
||||
OracleColumn,
|
||||
OracleColumnsResponse,
|
||||
OracleConstraint,
|
||||
} from "./base/types"
|
||||
import { OracleTable, OracleColumn, OracleColumnsResponse } from "./base/types"
|
||||
let oracledb: any
|
||||
try {
|
||||
oracledb = require("oracledb")
|
||||
|
@ -53,6 +50,7 @@ const SCHEMA: Integration = {
|
|||
type: "Relational",
|
||||
description:
|
||||
"Oracle Database is an object-relational database management system developed by Oracle Corporation",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
host: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -325,6 +323,30 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
|||
this.schemaErrors = final.errors
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
let connection
|
||||
try {
|
||||
connection = await this.getConnection()
|
||||
response.connected = true
|
||||
} catch (err: any) {
|
||||
response.connected = false
|
||||
response.error = err.message
|
||||
} finally {
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.close()
|
||||
} catch (err: any) {
|
||||
response.connected = false
|
||||
response.error = err.message
|
||||
}
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
private async internalQuery<T>(query: SqlQuery): Promise<Result<T>> {
|
||||
let connection
|
||||
try {
|
||||
|
|
|
@ -6,6 +6,8 @@ import {
|
|||
SqlQuery,
|
||||
Table,
|
||||
DatasourcePlus,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
import {
|
||||
getSqlQuery,
|
||||
|
@ -18,7 +20,7 @@ import Sql from "./base/sql"
|
|||
import { PostgresColumn } from "./base/types"
|
||||
import { escapeDangerousCharacters } from "../utilities"
|
||||
|
||||
const { Client, types } = require("pg")
|
||||
import { Client, types } from "pg"
|
||||
|
||||
// Return "date" and "timestamp" types as plain strings.
|
||||
// This lets us reference the original stored timezone.
|
||||
|
@ -50,6 +52,7 @@ const SCHEMA: Integration = {
|
|||
type: "Relational",
|
||||
description:
|
||||
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
host: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
|
@ -114,7 +117,7 @@ const SCHEMA: Integration = {
|
|||
}
|
||||
|
||||
class PostgresIntegration extends Sql implements DatasourcePlus {
|
||||
private readonly client: any
|
||||
private readonly client: Client
|
||||
private readonly config: PostgresConfig
|
||||
private index: number = 1
|
||||
private open: boolean
|
||||
|
@ -150,6 +153,21 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
|||
this.open = false
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
try {
|
||||
await this.openConnection()
|
||||
response.connected = true
|
||||
} catch (e: any) {
|
||||
response.error = e.message as string
|
||||
} finally {
|
||||
await this.closeConnection()
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
getBindingIdentifier(): string {
|
||||
return `$${this.index++}`
|
||||
}
|
||||
|
@ -163,7 +181,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
|||
if (!this.config.schema) {
|
||||
this.config.schema = "public"
|
||||
}
|
||||
this.client.query(`SET search_path TO ${this.config.schema}`)
|
||||
await this.client.query(`SET search_path TO ${this.config.schema}`)
|
||||
this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'`
|
||||
this.open = true
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { DatasourceFieldType, Integration, QueryType } from "@budibase/types"
|
||||
import {
|
||||
ConnectionInfo,
|
||||
DatasourceFeature,
|
||||
DatasourceFieldType,
|
||||
Integration,
|
||||
QueryType,
|
||||
} from "@budibase/types"
|
||||
import Redis from "ioredis"
|
||||
|
||||
interface RedisConfig {
|
||||
|
@ -11,9 +17,11 @@ interface RedisConfig {
|
|||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://redis.io/docs/",
|
||||
description: "",
|
||||
description:
|
||||
"Redis is a caching tool, providing powerful key-value store capabilities.",
|
||||
friendlyName: "Redis",
|
||||
type: "Non-relational",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
host: {
|
||||
type: "string",
|
||||
|
@ -86,7 +94,7 @@ const SCHEMA: Integration = {
|
|||
|
||||
class RedisIntegration {
|
||||
private readonly config: RedisConfig
|
||||
private client: any
|
||||
private client
|
||||
|
||||
constructor(config: RedisConfig) {
|
||||
this.config = config
|
||||
|
@ -99,6 +107,21 @@ class RedisIntegration {
|
|||
})
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
try {
|
||||
await this.client.ping()
|
||||
response.connected = true
|
||||
} catch (e: any) {
|
||||
response.error = e.message as string
|
||||
} finally {
|
||||
await this.disconnect()
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
return this.client.quit()
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@ import {
|
|||
QueryType,
|
||||
IntegrationBase,
|
||||
DatasourceFieldType,
|
||||
DatasourceFeature,
|
||||
ConnectionInfo,
|
||||
} from "@budibase/types"
|
||||
|
||||
const AWS = require("aws-sdk")
|
||||
const csv = require("csvtojson")
|
||||
import AWS from "aws-sdk"
|
||||
import csv from "csvtojson"
|
||||
|
||||
interface S3Config {
|
||||
region: string
|
||||
|
@ -22,6 +24,7 @@ const SCHEMA: Integration = {
|
|||
"Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance.",
|
||||
friendlyName: "Amazon S3",
|
||||
type: "Object store",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
region: {
|
||||
type: "string",
|
||||
|
@ -152,7 +155,7 @@ const SCHEMA: Integration = {
|
|||
|
||||
class S3Integration implements IntegrationBase {
|
||||
private readonly config: S3Config
|
||||
private client: any
|
||||
private client
|
||||
|
||||
constructor(config: S3Config) {
|
||||
this.config = config
|
||||
|
@ -165,6 +168,19 @@ class S3Integration implements IntegrationBase {
|
|||
this.client = new AWS.S3(this.config)
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
const response: ConnectionInfo = {
|
||||
connected: false,
|
||||
}
|
||||
try {
|
||||
await this.client.listBuckets().promise()
|
||||
response.connected = true
|
||||
} catch (e: any) {
|
||||
response.error = e.message as string
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async create(query: {
|
||||
bucket: string
|
||||
location: string
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { Integration, QueryType, SqlQuery } from "@budibase/types"
|
||||
import {
|
||||
ConnectionInfo,
|
||||
DatasourceFeature,
|
||||
Integration,
|
||||
QueryType,
|
||||
SqlQuery,
|
||||
} from "@budibase/types"
|
||||
import { Snowflake } from "snowflake-promise"
|
||||
|
||||
interface SnowflakeConfig {
|
||||
|
@ -16,6 +22,7 @@ const SCHEMA: Integration = {
|
|||
"Snowflake is a solution for data warehousing, data lakes, data engineering, data science, data application development, and securely sharing and consuming shared data.",
|
||||
friendlyName: "Snowflake",
|
||||
type: "Relational",
|
||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
||||
datasource: {
|
||||
account: {
|
||||
type: "string",
|
||||
|
@ -65,6 +72,18 @@ class SnowflakeIntegration {
|
|||
this.client = new Snowflake(config)
|
||||
}
|
||||
|
||||
async testConnection(): Promise<ConnectionInfo> {
|
||||
try {
|
||||
await this.client.connect()
|
||||
return { connected: true }
|
||||
} catch (e: any) {
|
||||
return {
|
||||
connected: false,
|
||||
error: e.message as string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async internalQuery(query: SqlQuery) {
|
||||
await this.client.connect()
|
||||
try {
|
||||
|
|
|
@ -33,7 +33,7 @@ export const backfill = async (appDb: any, timestamp: string | number) => {
|
|||
datasource = {
|
||||
type: "unknown",
|
||||
_id: query.datasourceId,
|
||||
source: SourceName.UNKNOWN,
|
||||
source: "unknown" as SourceName,
|
||||
}
|
||||
} else {
|
||||
throw e
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
import { cloneDeep } from "lodash/fp"
|
||||
import { getEnvironmentVariables } from "../../utils"
|
||||
import { getDefinitions, getDefinition } from "../../../integrations"
|
||||
import _ from "lodash"
|
||||
|
||||
const ENV_VAR_PREFIX = "env."
|
||||
|
||||
|
@ -41,7 +42,7 @@ async function enrichDatasourceWithValues(datasource: Datasource) {
|
|||
{ onlyFound: true }
|
||||
) as Datasource
|
||||
const definition = await getDefinition(processed.source)
|
||||
processed.config = checkDatasourceTypes(definition, processed.config)
|
||||
processed.config = checkDatasourceTypes(definition!, processed.config)
|
||||
return {
|
||||
datasource: processed,
|
||||
envVars: env as Record<string, string>,
|
||||
|
@ -147,6 +148,11 @@ export function mergeConfigs(update: Datasource, old: Datasource) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (old.config?.auth) {
|
||||
update.config = _.merge(old.config, update.config)
|
||||
}
|
||||
|
||||
// update back to actual passwords for everything else
|
||||
for (let [key, value] of Object.entries(update.config)) {
|
||||
if (value !== PASSWORD_REPLACEMENT) {
|
||||
|
|
|
@ -14,6 +14,15 @@ export interface CreateDatasourceRequest {
|
|||
fetchSchema?: boolean
|
||||
}
|
||||
|
||||
export interface VerifyDatasourceRequest {
|
||||
datasource: Datasource
|
||||
}
|
||||
|
||||
export interface VerifyDatasourceResponse {
|
||||
connected: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface UpdateDatasourceRequest extends Datasource {
|
||||
datasource: Datasource
|
||||
}
|
||||
|
|
|
@ -55,7 +55,6 @@ export enum SourceName {
|
|||
FIRESTORE = "FIRESTORE",
|
||||
REDIS = "REDIS",
|
||||
SNOWFLAKE = "SNOWFLAKE",
|
||||
UNKNOWN = "unknown",
|
||||
}
|
||||
|
||||
export enum IncludeRelationship {
|
||||
|
@ -74,6 +73,10 @@ export enum FilterType {
|
|||
ONE_OF = "oneOf",
|
||||
}
|
||||
|
||||
export enum DatasourceFeature {
|
||||
CONNECTION_CHECKING = "connection",
|
||||
}
|
||||
|
||||
export interface StepDefinition {
|
||||
key: string
|
||||
template: string
|
||||
|
@ -112,6 +115,7 @@ export interface Integration {
|
|||
docs: string
|
||||
plus?: boolean
|
||||
auth?: { type: string }
|
||||
features?: DatasourceFeature[]
|
||||
relationships?: boolean
|
||||
description: string
|
||||
friendlyName: string
|
||||
|
@ -124,11 +128,17 @@ export interface Integration {
|
|||
extra?: ExtraQueryConfig
|
||||
}
|
||||
|
||||
export type ConnectionInfo = {
|
||||
connected: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface IntegrationBase {
|
||||
create?(query: any): Promise<any[] | any>
|
||||
read?(query: any): Promise<any[] | any>
|
||||
update?(query: any): Promise<any[] | any>
|
||||
delete?(query: any): Promise<any[] | any>
|
||||
testConnection?(): Promise<ConnectionInfo>
|
||||
}
|
||||
|
||||
export interface DatasourcePlus extends IntegrationBase {
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
"test:notify": "node scripts/testResultsWebhook",
|
||||
"test:smoke": "yarn run test --testPathIgnorePatterns=/.+\\.integration\\.spec\\.ts",
|
||||
"test:ci": "start-server-and-test dev:built http://localhost:4001/health test:smoke",
|
||||
"serve": "start-server-and-test dev:built http://localhost:4001/health",
|
||||
"dev:built": "cd ../ && yarn dev:built"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@budibase/types": "^2.3.17",
|
||||
"@trendyol/jest-testcontainers": "^2.1.1",
|
||||
"@types/jest": "29.0.0",
|
||||
"@types/node-fetch": "2.6.2",
|
||||
"chance": "1.1.8",
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import { GenericContainer, Wait } from "testcontainers"
|
||||
import arangodb from "../../../../packages/server/src/integrations/arangodb"
|
||||
import { generator } from "../../shared"
|
||||
|
||||
jest.unmock("arangojs")
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("arangodb", () => {
|
||||
let connectionSettings: {
|
||||
user: string
|
||||
password: string
|
||||
url: string
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const user = "root"
|
||||
const password = generator.hash()
|
||||
const container = await new GenericContainer("arangodb")
|
||||
.withExposedPorts(8529)
|
||||
.withEnv("ARANGO_ROOT_PASSWORD", password)
|
||||
.withWaitStrategy(
|
||||
Wait.forLogMessage("is ready for business. Have fun!")
|
||||
)
|
||||
.start()
|
||||
|
||||
connectionSettings = {
|
||||
user,
|
||||
password,
|
||||
url: `http://${container.getContainerIpAddress()}:${container.getMappedPort(
|
||||
8529
|
||||
)}`,
|
||||
}
|
||||
})
|
||||
|
||||
it("test valid connection string", async () => {
|
||||
const integration = new arangodb.integration({
|
||||
url: connectionSettings.url,
|
||||
username: connectionSettings.user,
|
||||
password: connectionSettings.password,
|
||||
databaseName: "",
|
||||
collection: "",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test wrong password", async () => {
|
||||
const integration = new arangodb.integration({
|
||||
url: connectionSettings.url,
|
||||
username: connectionSettings.user,
|
||||
password: "wrong",
|
||||
databaseName: "",
|
||||
collection: "",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error: "not authorized to execute this request",
|
||||
})
|
||||
})
|
||||
|
||||
it("test wrong url", async () => {
|
||||
const integration = new arangodb.integration({
|
||||
url: "http://not.here",
|
||||
username: connectionSettings.user,
|
||||
password: connectionSettings.password,
|
||||
databaseName: "",
|
||||
collection: "",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error: "getaddrinfo ENOTFOUND not.here",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,67 @@
|
|||
import { GenericContainer } from "testcontainers"
|
||||
|
||||
import couchdb from "../../../../packages/server/src/integrations/couchdb"
|
||||
import { generator } from "../../shared"
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("couchdb", () => {
|
||||
let url: string
|
||||
|
||||
beforeAll(async () => {
|
||||
const user = generator.first()
|
||||
const password = generator.hash()
|
||||
|
||||
const container = await new GenericContainer("budibase/couchdb")
|
||||
.withExposedPorts(5984)
|
||||
.withEnv("COUCHDB_USER", user)
|
||||
.withEnv("COUCHDB_PASSWORD", password)
|
||||
.start()
|
||||
|
||||
const host = container.getContainerIpAddress()
|
||||
const port = container.getMappedPort(5984)
|
||||
|
||||
await container.exec([
|
||||
`curl`,
|
||||
`-u`,
|
||||
`${user}:${password}`,
|
||||
`-X`,
|
||||
`PUT`,
|
||||
`localhost:5984/db`,
|
||||
])
|
||||
url = `http://${user}:${password}@${host}:${port}`
|
||||
})
|
||||
|
||||
it("test valid connection string", async () => {
|
||||
const integration = new couchdb.integration({
|
||||
url,
|
||||
database: "db",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test invalid database", async () => {
|
||||
const integration = new couchdb.integration({
|
||||
url,
|
||||
database: "random_db",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("test invalid url", async () => {
|
||||
const integration = new couchdb.integration({
|
||||
url: "http://invalid:123",
|
||||
database: "any",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error:
|
||||
"request to http://invalid:123/any failed, reason: getaddrinfo ENOTFOUND invalid",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,63 @@
|
|||
import { GenericContainer } from "testcontainers"
|
||||
import { env } from "@budibase/backend-core"
|
||||
|
||||
import dynamodb from "../../../../packages/server/src/integrations/dynamodb"
|
||||
import { generator } from "../../shared"
|
||||
|
||||
jest.unmock("aws-sdk")
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("dynamodb", () => {
|
||||
let connectionSettings: {
|
||||
user: string
|
||||
password: string
|
||||
url: string
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const user = "root"
|
||||
const password = generator.hash()
|
||||
const container = await new GenericContainer("amazon/dynamodb-local")
|
||||
.withExposedPorts(8000)
|
||||
.start()
|
||||
|
||||
connectionSettings = {
|
||||
user,
|
||||
password,
|
||||
url: `http://${container.getContainerIpAddress()}:${container.getMappedPort(
|
||||
8000
|
||||
)}`,
|
||||
}
|
||||
env._set("AWS_ACCESS_KEY_ID", "mocked_key")
|
||||
env._set("AWS_SECRET_ACCESS_KEY", "mocked_secret")
|
||||
})
|
||||
|
||||
it("test valid connection string", async () => {
|
||||
const integration = new dynamodb.integration({
|
||||
endpoint: connectionSettings.url,
|
||||
region: "",
|
||||
accessKeyId: "",
|
||||
secretAccessKey: "",
|
||||
})
|
||||
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test wrong endpoint", async () => {
|
||||
const integration = new dynamodb.integration({
|
||||
endpoint: "http://wrong.url:2880",
|
||||
region: "",
|
||||
accessKeyId: "",
|
||||
secretAccessKey: "",
|
||||
})
|
||||
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error:
|
||||
"Inaccessible host: `wrong.url' at port `undefined'. This service may not be available in the `eu-west-1' region.",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,34 @@
|
|||
import { ElasticsearchContainer } from "testcontainers"
|
||||
import elastic from "../../../../packages/server/src/integrations/elasticsearch"
|
||||
|
||||
jest.unmock("@elastic/elasticsearch")
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("elastic search", () => {
|
||||
let url: string
|
||||
|
||||
beforeAll(async () => {
|
||||
const container = await new ElasticsearchContainer().start()
|
||||
url = container.getHttpUrl()
|
||||
})
|
||||
|
||||
it("test valid connection string", async () => {
|
||||
const integration = new elastic.integration({
|
||||
url,
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test wrong connection string", async () => {
|
||||
const integration = new elastic.integration({
|
||||
url: `http://localhost:5656`,
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error: "connect ECONNREFUSED 127.0.0.1:5656",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,100 @@
|
|||
import { GenericContainer } from "testcontainers"
|
||||
import mongo from "../../../../packages/server/src/integrations/mongodb"
|
||||
import { generator } from "../../shared"
|
||||
|
||||
jest.unmock("mongodb")
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("mongo", () => {
|
||||
let connectionSettings: {
|
||||
user: string
|
||||
password: string
|
||||
host: string
|
||||
port: number
|
||||
}
|
||||
|
||||
function getConnectionString(
|
||||
settings: Partial<typeof connectionSettings> = {}
|
||||
) {
|
||||
const { user, password, host, port } = {
|
||||
...connectionSettings,
|
||||
...settings,
|
||||
}
|
||||
return `mongodb://${user}:${password}@${host}:${port}`
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const user = generator.name()
|
||||
const password = generator.hash()
|
||||
const container = await new GenericContainer("mongo")
|
||||
.withExposedPorts(27017)
|
||||
.withEnv("MONGO_INITDB_ROOT_USERNAME", user)
|
||||
.withEnv("MONGO_INITDB_ROOT_PASSWORD", password)
|
||||
.start()
|
||||
|
||||
connectionSettings = {
|
||||
user,
|
||||
password,
|
||||
host: container.getContainerIpAddress(),
|
||||
port: container.getMappedPort(27017),
|
||||
}
|
||||
})
|
||||
|
||||
it("test valid connection string", async () => {
|
||||
const integration = new mongo.integration({
|
||||
connectionString: getConnectionString(),
|
||||
db: "",
|
||||
tlsCertificateFile: "",
|
||||
tlsCertificateKeyFile: "",
|
||||
tlsCAFile: "",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test invalid password", async () => {
|
||||
const integration = new mongo.integration({
|
||||
connectionString: getConnectionString({ password: "wrong" }),
|
||||
db: "",
|
||||
tlsCertificateFile: "",
|
||||
tlsCertificateKeyFile: "",
|
||||
tlsCAFile: "",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error: "Authentication failed.",
|
||||
})
|
||||
})
|
||||
|
||||
it("test invalid username", async () => {
|
||||
const integration = new mongo.integration({
|
||||
connectionString: getConnectionString({ user: "wrong" }),
|
||||
db: "",
|
||||
tlsCertificateFile: "",
|
||||
tlsCertificateKeyFile: "",
|
||||
tlsCAFile: "",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error: "Authentication failed.",
|
||||
})
|
||||
})
|
||||
|
||||
it("test invalid connection", async () => {
|
||||
const integration = new mongo.integration({
|
||||
connectionString: getConnectionString({ host: "http://nothinghere" }),
|
||||
db: "",
|
||||
tlsCertificateFile: "",
|
||||
tlsCertificateKeyFile: "",
|
||||
tlsCAFile: "",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error: "Error: getaddrinfo ENOTFOUND http",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,65 @@
|
|||
import { GenericContainer, Wait } from "testcontainers"
|
||||
import { Duration, TemporalUnit } from "node-duration"
|
||||
|
||||
import mssql from "../../../../packages/server/src/integrations/microsoftSqlServer"
|
||||
|
||||
jest.unmock("mssql")
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("mssql", () => {
|
||||
let host: string, port: number
|
||||
|
||||
const password = "Str0Ng_p@ssW0rd!"
|
||||
|
||||
beforeAll(async () => {
|
||||
const container = await new GenericContainer(
|
||||
"mcr.microsoft.com/mssql/server"
|
||||
)
|
||||
.withExposedPorts(1433)
|
||||
.withEnv("ACCEPT_EULA", "Y")
|
||||
.withEnv("MSSQL_SA_PASSWORD", password)
|
||||
.withEnv("MSSQL_PID", "Developer")
|
||||
.withWaitStrategy(Wait.forHealthCheck())
|
||||
.withHealthCheck({
|
||||
test: `/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "${password}" -Q "SELECT 1" -b -o /dev/null`,
|
||||
interval: new Duration(1000, TemporalUnit.MILLISECONDS),
|
||||
timeout: new Duration(3, TemporalUnit.SECONDS),
|
||||
retries: 20,
|
||||
startPeriod: new Duration(100, TemporalUnit.MILLISECONDS),
|
||||
})
|
||||
.start()
|
||||
|
||||
host = container.getContainerIpAddress()
|
||||
port = container.getMappedPort(1433)
|
||||
})
|
||||
|
||||
it("test valid connection string", async () => {
|
||||
const integration = new mssql.integration({
|
||||
user: "sa",
|
||||
password,
|
||||
server: host,
|
||||
port: port,
|
||||
database: "master",
|
||||
schema: "dbo",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test invalid password", async () => {
|
||||
const integration = new mssql.integration({
|
||||
user: "sa",
|
||||
password: "wrong_pwd",
|
||||
server: host,
|
||||
port: port,
|
||||
database: "master",
|
||||
schema: "dbo",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error: "ConnectionError: Login failed for user 'sa'.",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,70 @@
|
|||
import { GenericContainer } from "testcontainers"
|
||||
import mysql from "../../../../packages/server/src/integrations/mysql"
|
||||
|
||||
jest.unmock("mysql2/promise")
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("mysql", () => {
|
||||
let host: string
|
||||
let port: number
|
||||
|
||||
beforeAll(async () => {
|
||||
const container = await new GenericContainer("mysql")
|
||||
.withExposedPorts(3306)
|
||||
.withEnv("MYSQL_ROOT_PASSWORD", "admin")
|
||||
.withEnv("MYSQL_DATABASE", "db")
|
||||
.withEnv("MYSQL_USER", "user")
|
||||
.withEnv("MYSQL_PASSWORD", "password")
|
||||
.start()
|
||||
|
||||
host = container.getContainerIpAddress()
|
||||
port = container.getMappedPort(3306)
|
||||
})
|
||||
|
||||
it("test valid connection string", async () => {
|
||||
const integration = new mysql.integration({
|
||||
host,
|
||||
port,
|
||||
user: "user",
|
||||
database: "db",
|
||||
password: "password",
|
||||
rejectUnauthorized: true,
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test invalid database", async () => {
|
||||
const integration = new mysql.integration({
|
||||
host,
|
||||
port,
|
||||
user: "user",
|
||||
database: "test",
|
||||
password: "password",
|
||||
rejectUnauthorized: true,
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error: "Access denied for user 'user'@'%' to database 'test'",
|
||||
})
|
||||
})
|
||||
|
||||
it("test invalid password", async () => {
|
||||
const integration = new mysql.integration({
|
||||
host,
|
||||
port,
|
||||
user: "root",
|
||||
database: "test",
|
||||
password: "wrong",
|
||||
rejectUnauthorized: true,
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error:
|
||||
"Access denied for user 'root'@'172.17.0.1' (using password: YES)",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,53 @@
|
|||
import { GenericContainer } from "testcontainers"
|
||||
|
||||
jest.unmock("pg")
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("postgres", () => {
|
||||
let host: string
|
||||
let port: number
|
||||
|
||||
beforeAll(async () => {
|
||||
const container = await new GenericContainer("postgres")
|
||||
.withExposedPorts(5432)
|
||||
.withEnv("POSTGRES_PASSWORD", "password")
|
||||
.start()
|
||||
|
||||
host = container.getContainerIpAddress()
|
||||
port = container.getMappedPort(5432)
|
||||
})
|
||||
|
||||
it("test valid connection string", async () => {
|
||||
const integration = new postgres.integration({
|
||||
host,
|
||||
port,
|
||||
database: "postgres",
|
||||
user: "postgres",
|
||||
password: "password",
|
||||
schema: "public",
|
||||
ssl: false,
|
||||
rejectUnauthorized: false,
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test invalid connection string", async () => {
|
||||
const integration = new postgres.integration({
|
||||
host,
|
||||
port,
|
||||
database: "postgres",
|
||||
user: "wrong",
|
||||
password: "password",
|
||||
schema: "public",
|
||||
ssl: false,
|
||||
rejectUnauthorized: false,
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error: 'password authentication failed for user "wrong"',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,72 @@
|
|||
import redis from "../../../../packages/server/src/integrations/redis"
|
||||
import { GenericContainer } from "testcontainers"
|
||||
import { generator } from "../../shared"
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("redis", () => {
|
||||
describe("unsecured", () => {
|
||||
let host: string
|
||||
let port: number
|
||||
|
||||
beforeAll(async () => {
|
||||
const container = await new GenericContainer("redis")
|
||||
.withExposedPorts(6379)
|
||||
.start()
|
||||
|
||||
host = container.getContainerIpAddress()
|
||||
port = container.getMappedPort(6379)
|
||||
})
|
||||
|
||||
it("test valid connection", async () => {
|
||||
const integration = new redis.integration({
|
||||
host,
|
||||
port,
|
||||
username: "",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test invalid connection even with wrong user/password", async () => {
|
||||
const integration = new redis.integration({
|
||||
host,
|
||||
port,
|
||||
username: generator.name(),
|
||||
password: generator.hash(),
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error:
|
||||
"WRONGPASS invalid username-password pair or user is disabled.",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("secured", () => {
|
||||
let host: string
|
||||
let port: number
|
||||
|
||||
beforeAll(async () => {
|
||||
const container = await new GenericContainer("redis")
|
||||
.withExposedPorts(6379)
|
||||
.withCmd(["redis-server", "--requirepass", "P@ssW0rd!"])
|
||||
.start()
|
||||
|
||||
host = container.getContainerIpAddress()
|
||||
port = container.getMappedPort(6379)
|
||||
})
|
||||
|
||||
it("test valid connection", async () => {
|
||||
const integration = new redis.integration({
|
||||
host,
|
||||
port,
|
||||
username: "",
|
||||
password: "P@ssW0rd!",
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,52 @@
|
|||
import s3 from "../../../../packages/server/src/integrations/s3"
|
||||
import { GenericContainer } from "testcontainers"
|
||||
|
||||
jest.unmock("aws-sdk")
|
||||
|
||||
describe("datasource validators", () => {
|
||||
describe("s3", () => {
|
||||
let host: string
|
||||
let port: number
|
||||
|
||||
beforeAll(async () => {
|
||||
const container = await new GenericContainer("localstack/localstack")
|
||||
.withExposedPorts(4566)
|
||||
.withEnv("SERVICES", "s3")
|
||||
.withEnv("DEFAULT_REGION", "eu-west-1")
|
||||
.withEnv("AWS_ACCESS_KEY_ID", "testkey")
|
||||
.withEnv("AWS_SECRET_ACCESS_KEY", "testsecret")
|
||||
.start()
|
||||
|
||||
host = container.getContainerIpAddress()
|
||||
port = container.getMappedPort(4566)
|
||||
})
|
||||
|
||||
it("test valid connection", async () => {
|
||||
const integration = new s3.integration({
|
||||
region: "eu-west-1",
|
||||
accessKeyId: "testkey",
|
||||
secretAccessKey: "testsecret",
|
||||
s3ForcePathStyle: false,
|
||||
endpoint: `http://${host}:${port}`,
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({ connected: true })
|
||||
})
|
||||
|
||||
it("test wrong endpoint", async () => {
|
||||
const integration = new s3.integration({
|
||||
region: "eu-west-2",
|
||||
accessKeyId: "testkey",
|
||||
secretAccessKey: "testsecret",
|
||||
s3ForcePathStyle: false,
|
||||
endpoint: `http://wrong:123`,
|
||||
})
|
||||
const result = await integration.testConnection()
|
||||
expect(result).toEqual({
|
||||
connected: false,
|
||||
error:
|
||||
"Inaccessible host: `wrong' at port `undefined'. This service may not be available in the `eu-west-2' region.",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,3 +1,3 @@
|
|||
const Chance = require("chance")
|
||||
import Chance from "chance"
|
||||
|
||||
export default new Chance()
|
||||
|
|
|
@ -304,6 +304,11 @@
|
|||
"@babel/helper-validator-identifier" "^7.18.6"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@balena/dockerignore@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d"
|
||||
integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==
|
||||
|
||||
"@bcoe/v8-coverage@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
|
@ -779,6 +784,15 @@
|
|||
request "^2.88.0"
|
||||
webfinger "^0.4.2"
|
||||
|
||||
"@trendyol/jest-testcontainers@^2.1.1":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@trendyol/jest-testcontainers/-/jest-testcontainers-2.1.1.tgz#dced95cf9c37b75efe0a65db9b75ae8912f2f14a"
|
||||
integrity sha512-4iAc2pMsev4BTUzoA7jO1VvbTOU2N3juQUYa8TwiSPXPuQtxKwV9WB9ZEP+JQ+Pj15YqfGOXp5H0WNMPtapjiA==
|
||||
dependencies:
|
||||
cwd "^0.10.0"
|
||||
node-duration "^1.0.4"
|
||||
testcontainers "4.7.0"
|
||||
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
|
||||
|
@ -832,6 +846,13 @@
|
|||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/dockerode@^2.5.34":
|
||||
version "2.5.34"
|
||||
resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-2.5.34.tgz#9adb884f7cc6c012a6eb4b2ad794cc5d01439959"
|
||||
integrity sha512-LcbLGcvcBwBAvjH9UrUI+4qotY+A5WCer5r43DR5XHv2ZIEByNXFdPLo1XxR+v/BjkGjlggW8qUiXuVEhqfkpA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/graceful-fs@^4.1.3":
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
|
||||
|
@ -1006,6 +1027,11 @@ ansi-styles@^5.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
any-promise@^1.1.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
|
||||
|
||||
anymatch@^3.0.3:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
|
@ -1044,7 +1070,7 @@ argsarray@0.0.1:
|
|||
resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb"
|
||||
integrity sha512-u96dg2GcAKtpTrBdDoFIM7PjcBA+6rSP0OR94MOReNRyUECL6MtQt5XXmRr4qrftYaef9+l5hcpO5te7sML1Cg==
|
||||
|
||||
asn1@~0.2.3:
|
||||
asn1@^0.2.6, asn1@~0.2.3:
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
|
||||
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
|
||||
|
@ -1199,7 +1225,7 @@ base64url@3.x.x, base64url@^3.0.1:
|
|||
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
|
||||
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
|
||||
|
@ -1304,6 +1330,11 @@ buffer@^5.5.0, buffer@^5.6.0:
|
|||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
buildcheck@~0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
|
||||
integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==
|
||||
|
||||
bull@4.10.1:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/bull/-/bull-4.10.1.tgz#f14974b6089358b62b495a2cbf838aadc098e43f"
|
||||
|
@ -1319,6 +1350,11 @@ bull@4.10.1:
|
|||
semver "^7.3.2"
|
||||
uuid "^8.3.0"
|
||||
|
||||
byline@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
|
||||
integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==
|
||||
|
||||
cache-content-type@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c"
|
||||
|
@ -1546,6 +1582,14 @@ correlation-id@4.0.0:
|
|||
dependencies:
|
||||
uuid "^8.3.1"
|
||||
|
||||
cpu-features@~0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.7.tgz#81ba93e1d0a729fd25132a54c3ff689c37b542f7"
|
||||
integrity sha512-fjzFmsUKKCrC9GrM1eQTvQx18e+kjXFzjRLvJPNEDjk31+bJ6ZiV6uchv/hzbzXVIgbWdrEyyX1IFKwse65+8w==
|
||||
dependencies:
|
||||
buildcheck "~0.0.6"
|
||||
nan "^2.17.0"
|
||||
|
||||
create-require@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||
|
@ -1572,6 +1616,14 @@ crypt@0.0.2:
|
|||
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
|
||||
integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
|
||||
|
||||
cwd@^0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/cwd/-/cwd-0.10.0.tgz#172400694057c22a13b0cf16162c7e4b7a7fe567"
|
||||
integrity sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==
|
||||
dependencies:
|
||||
find-pkg "^0.1.2"
|
||||
fs-exists-sync "^0.1.0"
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
|
@ -1676,6 +1728,32 @@ diff@^4.0.1:
|
|||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
docker-compose@^0.23.5:
|
||||
version "0.23.19"
|
||||
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.19.tgz#9947726e2fe67bdfa9e8efe1ff15aa0de2e10eb8"
|
||||
integrity sha512-v5vNLIdUqwj4my80wxFDkNH+4S85zsRuH29SO7dCWVWPCMt/ohZBsGN6g6KXWifT0pzQ7uOxqEKCYCDPJ8Vz4g==
|
||||
dependencies:
|
||||
yaml "^1.10.2"
|
||||
|
||||
docker-modem@^3.0.0:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-3.0.8.tgz#ef62c8bdff6e8a7d12f0160988c295ea8705e77a"
|
||||
integrity sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
readable-stream "^3.5.0"
|
||||
split-ca "^1.0.1"
|
||||
ssh2 "^1.11.0"
|
||||
|
||||
dockerode@^3.2.1:
|
||||
version "3.3.5"
|
||||
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629"
|
||||
integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==
|
||||
dependencies:
|
||||
"@balena/dockerignore" "^1.0.2"
|
||||
docker-modem "^3.0.0"
|
||||
tar-fs "~2.0.1"
|
||||
|
||||
dotenv@16.0.1:
|
||||
version "16.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d"
|
||||
|
@ -1844,6 +1922,13 @@ exit@^0.1.2:
|
|||
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
||||
integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
|
||||
|
||||
expand-tilde@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449"
|
||||
integrity sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==
|
||||
dependencies:
|
||||
os-homedir "^1.0.1"
|
||||
|
||||
expect@^29.0.0:
|
||||
version "29.0.2"
|
||||
resolved "https://registry.yarnpkg.com/expect/-/expect-29.0.2.tgz#22c7132400f60444b427211f1d6bb604a9ab2420"
|
||||
|
@ -1919,6 +2004,21 @@ fill-range@^7.0.1:
|
|||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
find-file-up@^0.1.2:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/find-file-up/-/find-file-up-0.1.3.tgz#cf68091bcf9f300a40da411b37da5cce5a2fbea0"
|
||||
integrity sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==
|
||||
dependencies:
|
||||
fs-exists-sync "^0.1.0"
|
||||
resolve-dir "^0.1.0"
|
||||
|
||||
find-pkg@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/find-pkg/-/find-pkg-0.1.2.tgz#1bdc22c06e36365532e2a248046854b9788da557"
|
||||
integrity sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==
|
||||
dependencies:
|
||||
find-file-up "^0.1.2"
|
||||
|
||||
find-up@^4.0.0, find-up@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
|
||||
|
@ -1984,6 +2084,11 @@ fs-constants@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
fs-exists-sync@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
|
||||
integrity sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==
|
||||
|
||||
fs-minipass@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
||||
|
@ -2062,7 +2167,7 @@ getpass@^0.1.1:
|
|||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
glob@^7.1.3, glob@^7.1.4:
|
||||
glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
|
||||
|
@ -2074,6 +2179,24 @@ glob@^7.1.3, glob@^7.1.4:
|
|||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
global-modules@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d"
|
||||
integrity sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==
|
||||
dependencies:
|
||||
global-prefix "^0.1.4"
|
||||
is-windows "^0.2.0"
|
||||
|
||||
global-prefix@^0.1.4:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f"
|
||||
integrity sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==
|
||||
dependencies:
|
||||
homedir-polyfill "^1.0.0"
|
||||
ini "^1.3.4"
|
||||
is-windows "^0.2.0"
|
||||
which "^1.2.12"
|
||||
|
||||
globals@^11.1.0:
|
||||
version "11.12.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||
|
@ -2131,6 +2254,13 @@ has@^1.0.3:
|
|||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
homedir-polyfill@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
|
||||
integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
|
||||
dependencies:
|
||||
parse-passwd "^1.0.0"
|
||||
|
||||
html-escaper@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||
|
@ -2230,6 +2360,11 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i
|
|||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ini@^1.3.4:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
ioredis@4.28.0:
|
||||
version "4.28.0"
|
||||
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.0.tgz#5a2be3f37ff2075e2332f280eaeb02ab4d9ff0d3"
|
||||
|
@ -2318,6 +2453,11 @@ is-typedarray@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
|
||||
|
||||
is-windows@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
|
||||
integrity sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
|
@ -3332,6 +3472,11 @@ msgpackr@^1.5.2:
|
|||
optionalDependencies:
|
||||
msgpackr-extract "^3.0.0"
|
||||
|
||||
nan@^2.17.0:
|
||||
version "2.17.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
|
||||
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
|
||||
|
||||
napi-macros@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
|
||||
|
@ -3367,6 +3512,11 @@ node-addon-api@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
||||
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
|
||||
|
||||
node-duration@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/node-duration/-/node-duration-1.0.4.tgz#3e94ecc0e473691c89c4560074503362071cecac"
|
||||
integrity sha512-eUXYNSY7DL53vqfTosggWkvyIW3bhAcqBDIlolgNYlZhianXTrCL50rlUJWD1eRqkIxMppXTfiFbp+9SjpPrgA==
|
||||
|
||||
node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
|
@ -3490,6 +3640,11 @@ only@~0.0.2:
|
|||
resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
|
||||
integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==
|
||||
|
||||
os-homedir@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
|
||||
integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==
|
||||
|
||||
p-finally@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
|
||||
|
@ -3543,6 +3698,11 @@ parse-json@^5.2.0:
|
|||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
parse-passwd@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
|
||||
integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==
|
||||
|
||||
parseurl@^1.3.2, parseurl@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
|
@ -3982,6 +4142,15 @@ readable-stream@1.1.14, readable-stream@^1.0.27-1:
|
|||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@^3.5.0:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
|
||||
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@~0.0.2:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-0.0.4.tgz#f32d76e3fb863344a548d79923007173665b3b8d"
|
||||
|
@ -4077,6 +4246,14 @@ resolve-cwd@^3.0.0:
|
|||
dependencies:
|
||||
resolve-from "^5.0.0"
|
||||
|
||||
resolve-dir@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e"
|
||||
integrity sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==
|
||||
dependencies:
|
||||
expand-tilde "^1.2.2"
|
||||
global-modules "^0.2.3"
|
||||
|
||||
resolve-from@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
|
||||
|
@ -4238,6 +4415,11 @@ spark-md5@3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc"
|
||||
integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==
|
||||
|
||||
split-ca@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6"
|
||||
integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==
|
||||
|
||||
split2@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493"
|
||||
|
@ -4257,6 +4439,17 @@ sprintf-js@~1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
|
||||
|
||||
ssh2@^1.11.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.13.0.tgz#9b53a07534fa72283ada471b82395a3b3c875934"
|
||||
integrity sha512-CIZBFRRY1y9mAZSqBGFE4EB4dNJad2ysT2PqO8OpkiI3UTB/gUZwE5EaN16qVyQ6s/M7EgC/iaV/MnjdlvnuzA==
|
||||
dependencies:
|
||||
asn1 "^0.2.6"
|
||||
bcrypt-pbkdf "^1.0.2"
|
||||
optionalDependencies:
|
||||
cpu-features "~0.0.7"
|
||||
nan "^2.17.0"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.17.0"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
|
||||
|
@ -4314,6 +4507,13 @@ stream-combiner@~0.0.4:
|
|||
dependencies:
|
||||
duplexer "~0.1.1"
|
||||
|
||||
stream-to-array@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353"
|
||||
integrity sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==
|
||||
dependencies:
|
||||
any-promise "^1.1.0"
|
||||
|
||||
string-length@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
|
||||
|
@ -4403,7 +4603,7 @@ supports-preserve-symlinks-flag@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
tar-fs@2.1.1:
|
||||
tar-fs@2.1.1, tar-fs@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
|
||||
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
|
||||
|
@ -4413,7 +4613,17 @@ tar-fs@2.1.1:
|
|||
pump "^3.0.0"
|
||||
tar-stream "^2.1.4"
|
||||
|
||||
tar-stream@^2.1.4:
|
||||
tar-fs@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2"
|
||||
integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==
|
||||
dependencies:
|
||||
chownr "^1.1.1"
|
||||
mkdirp-classic "^0.5.2"
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.0.0"
|
||||
|
||||
tar-stream@^2.0.0, tar-stream@^2.1.4:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
|
||||
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
|
||||
|
@ -4445,6 +4655,23 @@ test-exclude@^6.0.0:
|
|||
glob "^7.1.4"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
testcontainers@4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-4.7.0.tgz#5a9a864b1b0cc86984086dcc737c2f5e73490cf3"
|
||||
integrity sha512-5SrG9RMfDRRZig34fDZeMcGD5i3lHCOJzn0kjouyK4TiEWjZB3h7kCk8524lwNRHROFE1j6DGjceonv/5hl5ag==
|
||||
dependencies:
|
||||
"@types/dockerode" "^2.5.34"
|
||||
byline "^5.0.0"
|
||||
debug "^4.1.1"
|
||||
docker-compose "^0.23.5"
|
||||
dockerode "^3.2.1"
|
||||
get-port "^5.1.1"
|
||||
glob "^7.1.6"
|
||||
node-duration "^1.0.4"
|
||||
slash "^3.0.0"
|
||||
stream-to-array "^2.3.0"
|
||||
tar-fs "^2.1.0"
|
||||
|
||||
through2@3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4"
|
||||
|
@ -4746,6 +4973,13 @@ whatwg-url@^5.0.0:
|
|||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
which@^1.2.12:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
|
@ -4824,6 +5058,11 @@ yallist@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yaml@^1.10.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||
|
||||
yargs-parser@^21.0.0, yargs-parser@^21.0.1:
|
||||
version "21.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -5211,6 +5211,15 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
|
||||
|
||||
"@types/pg@8.6.6":
|
||||
version "8.6.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.6.tgz#21cdf873a3e345a6e78f394677e3b3b1b543cb80"
|
||||
integrity sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
pg-protocol "*"
|
||||
pg-types "^2.2.0"
|
||||
|
||||
"@types/pouchdb-adapter-cordova-sqlite@*":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-cordova-sqlite/-/pouchdb-adapter-cordova-sqlite-1.0.1.tgz#49e5ee6df7cc0c23196fcb340f43a560e74eb1d6"
|
||||
|
@ -19085,7 +19094,7 @@ performance-now@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
|
||||
|
||||
pg-connection-string@2.5.0, pg-connection-string@^2.4.0:
|
||||
pg-connection-string@2.5.0, pg-connection-string@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
|
||||
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
|
||||
|
@ -19095,17 +19104,17 @@ pg-int8@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
|
||||
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
|
||||
|
||||
pg-pool@^3.2.2:
|
||||
pg-pool@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e"
|
||||
integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==
|
||||
|
||||
pg-protocol@^1.4.0:
|
||||
pg-protocol@*, pg-protocol@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833"
|
||||
integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==
|
||||
|
||||
pg-types@^2.1.0:
|
||||
pg-types@^2.1.0, pg-types@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
|
||||
integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
|
||||
|
@ -19116,16 +19125,16 @@ pg-types@^2.1.0:
|
|||
postgres-date "~1.0.4"
|
||||
postgres-interval "^1.1.0"
|
||||
|
||||
pg@8.5.1:
|
||||
version "8.5.1"
|
||||
resolved "https://registry.yarnpkg.com/pg/-/pg-8.5.1.tgz#34dcb15f6db4a29c702bf5031ef2e1e25a06a120"
|
||||
integrity sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw==
|
||||
pg@8.10.0:
|
||||
version "8.10.0"
|
||||
resolved "https://registry.yarnpkg.com/pg/-/pg-8.10.0.tgz#5b8379c9b4a36451d110fc8cd98fc325fe62ad24"
|
||||
integrity sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==
|
||||
dependencies:
|
||||
buffer-writer "2.0.0"
|
||||
packet-reader "1.0.0"
|
||||
pg-connection-string "^2.4.0"
|
||||
pg-pool "^3.2.2"
|
||||
pg-protocol "^1.4.0"
|
||||
pg-connection-string "^2.5.0"
|
||||
pg-pool "^3.6.0"
|
||||
pg-protocol "^1.6.0"
|
||||
pg-types "^2.1.0"
|
||||
pgpass "1.x"
|
||||
|
||||
|
|
Loading…
Reference in New Issue