First attempt and re-writing a portion of the server in typescript.
This commit is contained in:
parent
6d75a7acf3
commit
374081d720
|
@ -37,7 +37,8 @@
|
|||
"lint": "yarn run lint:eslint && yarn run lint:prettier",
|
||||
"lint:fix:eslint": "eslint --fix packages",
|
||||
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,svelte}\"",
|
||||
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
||||
"lint:fix:ts": "lerna run lint:fix",
|
||||
"lint:fix": "yarn run lint:fix:ts && yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
||||
"test:e2e": "lerna run cy:test",
|
||||
"test:e2e:ci": "lerna run cy:ci",
|
||||
"build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"emit": true,
|
||||
"key": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ ENV BUDIBASE_ENVIRONMENT=PRODUCTION
|
|||
# copy files and install dependencies
|
||||
COPY . ./
|
||||
RUN yarn
|
||||
RUN yarn build
|
||||
|
||||
EXPOSE 4001
|
||||
|
||||
|
|
|
@ -3,22 +3,25 @@
|
|||
"email": "hi@budibase.com",
|
||||
"version": "0.9.63",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/electron.js",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Budibase/budibase.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest --testPathIgnorePatterns=routes && yarn run test:integration",
|
||||
"test:integration": "jest --coverage --detectOpenHandles",
|
||||
"test:watch": "jest --watch",
|
||||
"build:docker": "docker build . -t app-service",
|
||||
"run:docker": "node src/index",
|
||||
"run:docker": "node dist/src/index.js",
|
||||
"dev:stack:up": "node scripts/dev/manage.js up",
|
||||
"dev:stack:down": "node scripts/dev/manage.js down",
|
||||
"dev:stack:nuke": "node scripts/dev/manage.js nuke",
|
||||
"dev:builder": "yarn run dev:stack:up && nodemon src/index.js",
|
||||
"dev:builder": "yarn run dev:stack:up && ts-node src/index.ts",
|
||||
"format": "prettier --config ../../.prettierrc.json 'src/**/*.ts' --write",
|
||||
"lint": "eslint --fix src/",
|
||||
"lint:fix": "yarn run format && yarn run lint",
|
||||
"initialise": "node scripts/initialise.js"
|
||||
},
|
||||
"jest": {
|
||||
|
@ -112,6 +115,11 @@
|
|||
"@babel/preset-env": "^7.14.4",
|
||||
"@budibase/standard-components": "^0.9.63",
|
||||
"@jest/test-sequencer": "^24.8.0",
|
||||
"@types/bull": "^3.15.1",
|
||||
"@types/koa": "^2.13.3",
|
||||
"@types/koa-router": "^7.4.2",
|
||||
"@types/node": "^15.12.4",
|
||||
"@typescript-eslint/parser": "^4.28.0",
|
||||
"babel-jest": "^27.0.2",
|
||||
"docker-compose": "^0.23.6",
|
||||
"eslint": "^6.8.0",
|
||||
|
@ -119,7 +127,10 @@
|
|||
"jest": "^24.8.0",
|
||||
"nodemon": "^2.0.4",
|
||||
"pouchdb-adapter-memory": "^7.2.1",
|
||||
"supertest": "^4.0.2"
|
||||
"prettier": "^2.3.1",
|
||||
"supertest": "^4.0.2",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^4.3.4"
|
||||
},
|
||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// need to load environment first
|
||||
import { ExtendableContext } from "koa"
|
||||
|
||||
const env = require("./environment")
|
||||
const CouchDB = require("./db")
|
||||
require("@budibase/auth").init(CouchDB)
|
||||
|
@ -40,7 +42,7 @@ app.use(
|
|||
|
||||
if (!env.isTest()) {
|
||||
const bullApp = bullboard.init()
|
||||
app.use(async (ctx, next) => {
|
||||
app.use(async (ctx: ExtendableContext, next: () => any) => {
|
||||
if (ctx.path.startsWith(bullboard.pathPrefix)) {
|
||||
ctx.status = 200
|
||||
ctx.respond = false
|
||||
|
@ -61,9 +63,9 @@ if (env.isProd()) {
|
|||
env._set("NODE_ENV", "production")
|
||||
Sentry.init()
|
||||
|
||||
app.on("error", (err, ctx) => {
|
||||
Sentry.withScope(function (scope) {
|
||||
scope.addEventProcessor(function (event) {
|
||||
app.on("error", (err: any, ctx: ExtendableContext) => {
|
||||
Sentry.withScope(function (scope: any) {
|
||||
scope.addEventProcessor(function (event: any) {
|
||||
return Sentry.Handlers.parseRequest(event, ctx.request)
|
||||
})
|
||||
Sentry.captureException(err)
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
export interface Table {
|
||||
_id: string,
|
||||
_rev?: string,
|
||||
type?: string,
|
||||
views?: {},
|
||||
name?: string,
|
||||
primary?: string[],
|
||||
schema: {
|
||||
[key: string]: {
|
||||
// TODO: replace with field types enum when done
|
||||
type: string,
|
||||
fieldName?: string,
|
||||
name: string,
|
||||
constraints?: {
|
||||
type?: string,
|
||||
email?: boolean,
|
||||
inclusion?: string[],
|
||||
length?: {
|
||||
minimum?: string | number,
|
||||
maximum?: string | number,
|
||||
},
|
||||
presence?: boolean,
|
||||
},
|
||||
}
|
||||
},
|
||||
primaryDisplay?: string,
|
||||
sourceId?: string,
|
||||
}
|
|
@ -17,6 +17,7 @@ exports.FieldTypes = {
|
|||
LINK: "link",
|
||||
FORMULA: "formula",
|
||||
AUTO: "auto",
|
||||
JSON: "json",
|
||||
}
|
||||
|
||||
exports.RelationshipTypes = {
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
exports.QUERY_TYPES = {
|
||||
SQL: "sql",
|
||||
JSON: "json",
|
||||
FIELDS: "fields",
|
||||
}
|
||||
|
||||
exports.FIELD_TYPES = {
|
||||
STRING: "string",
|
||||
BOOLEAN: "boolean",
|
||||
NUMBER: "number",
|
||||
PASSWORD: "password",
|
||||
LIST: "list",
|
||||
OBJECT: "object",
|
||||
JSON: "json",
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
const Airtable = require("airtable")
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://airtable.com/api",
|
||||
description:
|
||||
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.",
|
||||
friendlyName: "Airtable",
|
||||
datasource: {
|
||||
apiKey: {
|
||||
type: FIELD_TYPES.PASSWORD,
|
||||
default: "enter api key",
|
||||
required: true,
|
||||
},
|
||||
base: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "mybase",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
fields: {
|
||||
table: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
view: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
numRecords: {
|
||||
type: FIELD_TYPES.NUMBER,
|
||||
default: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
id: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
type: QUERY_TYPES.JSON,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class AirtableIntegration {
|
||||
constructor(config) {
|
||||
this.config = config
|
||||
this.client = new Airtable(config).base(config.base)
|
||||
}
|
||||
|
||||
async create(query) {
|
||||
const { table, json } = query
|
||||
|
||||
try {
|
||||
const records = await this.client(table).create([
|
||||
{
|
||||
fields: json,
|
||||
},
|
||||
])
|
||||
return records
|
||||
} catch (err) {
|
||||
console.error("Error writing to airtable", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async read(query) {
|
||||
try {
|
||||
const records = await this.client(query.table)
|
||||
.select({ maxRecords: query.numRecords || 10, view: query.view })
|
||||
.firstPage()
|
||||
return records.map(({ fields }) => fields)
|
||||
} catch (err) {
|
||||
console.error("Error writing to airtable", err)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async update(query) {
|
||||
const { table, id, json } = query
|
||||
|
||||
try {
|
||||
const records = await this.client(table).update([
|
||||
{
|
||||
id,
|
||||
fields: json,
|
||||
},
|
||||
])
|
||||
return records
|
||||
} catch (err) {
|
||||
console.error("Error writing to airtable", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async delete(query) {
|
||||
try {
|
||||
const records = await this.client(query.table).destroy(query.ids)
|
||||
return records
|
||||
} catch (err) {
|
||||
console.error("Error writing to airtable", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: AirtableIntegration,
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
import { Integration, DatasourceFieldTypes, QueryTypes } from "./base/definitions"
|
||||
|
||||
module AirtableModule {
|
||||
const Airtable = require("airtable")
|
||||
|
||||
interface AirtableConfig {
|
||||
apiKey: string
|
||||
base: string
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://airtable.com/api",
|
||||
description:
|
||||
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.",
|
||||
friendlyName: "Airtable",
|
||||
datasource: {
|
||||
apiKey: {
|
||||
type: DatasourceFieldTypes.PASSWORD,
|
||||
default: "enter api key",
|
||||
required: true,
|
||||
},
|
||||
base: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "mybase",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: QueryTypes.FIELDS,
|
||||
fields: {
|
||||
table: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
view: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
numRecords: {
|
||||
type: DatasourceFieldTypes.NUMBER,
|
||||
default: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
id: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
type: QueryTypes.JSON,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class AirtableIntegration {
|
||||
private config: AirtableConfig
|
||||
private client: any
|
||||
|
||||
constructor(config: AirtableConfig) {
|
||||
this.config = config
|
||||
this.client = new Airtable(config).base(config.base)
|
||||
}
|
||||
|
||||
async create(query: { table: any; json: any }) {
|
||||
const { table, json } = query
|
||||
|
||||
try {
|
||||
return await this.client(table).create([
|
||||
{
|
||||
fields: json,
|
||||
},
|
||||
])
|
||||
} catch (err) {
|
||||
console.error("Error writing to airtable", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async read(query: { table: any; numRecords: any; view: any }) {
|
||||
try {
|
||||
const records = await this.client(query.table)
|
||||
.select({ maxRecords: query.numRecords || 10, view: query.view })
|
||||
.firstPage()
|
||||
// @ts-ignore
|
||||
return records.map(({ fields }) => fields)
|
||||
} catch (err) {
|
||||
console.error("Error writing to airtable", err)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async update(query: { table: any; id: any; json: any }) {
|
||||
const { table, id, json } = query
|
||||
|
||||
try {
|
||||
return await this.client(table).update([
|
||||
{
|
||||
id,
|
||||
fields: json,
|
||||
},
|
||||
])
|
||||
} catch (err) {
|
||||
console.error("Error writing to airtable", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async delete(query: { table: any; ids: any }) {
|
||||
try {
|
||||
return await this.client(query.table).destroy(query.ids)
|
||||
} catch (err) {
|
||||
console.error("Error writing to airtable", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: AirtableIntegration,
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
const { Database, aql } = require("arangojs")
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://github.com/arangodb/arangojs",
|
||||
friendlyName: "ArangoDB",
|
||||
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. ",
|
||||
datasource: {
|
||||
url: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "http://localhost:8529",
|
||||
required: true,
|
||||
},
|
||||
username: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
password: {
|
||||
type: FIELD_TYPES.PASSWORD,
|
||||
required: true,
|
||||
},
|
||||
databaseName: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "_system",
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
read: {
|
||||
type: QUERY_TYPES.SQL,
|
||||
},
|
||||
create: {
|
||||
type: QUERY_TYPES.JSON,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class ArangoDBIntegration {
|
||||
constructor(config) {
|
||||
config.auth = {
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
}
|
||||
|
||||
this.config = config
|
||||
this.client = new Database(config)
|
||||
}
|
||||
|
||||
async read(query) {
|
||||
try {
|
||||
const result = await this.client.query(query.sql)
|
||||
return result.all()
|
||||
} catch (err) {
|
||||
console.error("Error querying arangodb", err.message)
|
||||
throw err
|
||||
} finally {
|
||||
this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async create(query) {
|
||||
const clc = this.client.collection(this.config.collection)
|
||||
try {
|
||||
const result = await this.client.query(
|
||||
aql`INSERT ${query.json} INTO ${clc} RETURN NEW`
|
||||
)
|
||||
return result.all()
|
||||
} catch (err) {
|
||||
console.error("Error querying arangodb", err.message)
|
||||
throw err
|
||||
} finally {
|
||||
this.client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: ArangoDBIntegration,
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
import { Integration, DatasourceFieldTypes, QueryTypes } from "./base/definitions"
|
||||
|
||||
module ArangoModule {
|
||||
const { Database, aql } = require("arangojs")
|
||||
|
||||
interface ArangodbConfig {
|
||||
url: string
|
||||
username: string
|
||||
password: string
|
||||
databaseName: string
|
||||
collection: string
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://github.com/arangodb/arangojs",
|
||||
friendlyName: "ArangoDB",
|
||||
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. ",
|
||||
datasource: {
|
||||
url: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "http://localhost:8529",
|
||||
required: true,
|
||||
},
|
||||
username: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
password: {
|
||||
type: DatasourceFieldTypes.PASSWORD,
|
||||
required: true,
|
||||
},
|
||||
databaseName: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "_system",
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
read: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
create: {
|
||||
type: QueryTypes.JSON,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class ArangoDBIntegration {
|
||||
private config: ArangodbConfig
|
||||
private client: any
|
||||
|
||||
constructor(config: ArangodbConfig) {
|
||||
const newConfig = {
|
||||
auth: {
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
},
|
||||
}
|
||||
|
||||
this.config = config
|
||||
this.client = new Database(newConfig)
|
||||
}
|
||||
|
||||
async read(query: { sql: any }) {
|
||||
try {
|
||||
const result = await this.client.query(query.sql)
|
||||
return result.all()
|
||||
} catch (err) {
|
||||
console.error("Error querying arangodb", err.message)
|
||||
throw err
|
||||
} finally {
|
||||
this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async create(query: { json: any }) {
|
||||
const clc = this.client.collection(this.config.collection)
|
||||
try {
|
||||
const result = await this.client.query(
|
||||
aql`INSERT ${query.json} INTO ${clc} RETURN NEW`
|
||||
)
|
||||
return result.all()
|
||||
} catch (err) {
|
||||
console.error("Error querying arangodb", err.message)
|
||||
throw err
|
||||
} finally {
|
||||
this.client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: ArangoDBIntegration,
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
exports.Operation = {
|
||||
CREATE: "CREATE",
|
||||
READ: "READ",
|
||||
UPDATE: "UPDATE",
|
||||
DELETE: "DELETE",
|
||||
}
|
||||
|
||||
exports.SortDirection = {
|
||||
ASCENDING: "ASCENDING",
|
||||
DESCENDING: "DESCENDING",
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
export enum Operation {
|
||||
CREATE = "CREATE",
|
||||
READ = "READ",
|
||||
UPDATE = "UPDATE",
|
||||
DELETE = "DELETE",
|
||||
}
|
||||
|
||||
export enum SortDirection {
|
||||
ASCENDING = "ASCENDING",
|
||||
DESCENDING = "DESCENDING",
|
||||
}
|
||||
|
||||
export enum QueryTypes {
|
||||
SQL = "sql",
|
||||
JSON = "json",
|
||||
FIELDS = "fields",
|
||||
}
|
||||
|
||||
export enum DatasourceFieldTypes {
|
||||
STRING = "string",
|
||||
BOOLEAN = "boolean",
|
||||
NUMBER = "number",
|
||||
PASSWORD = "password",
|
||||
LIST = "list",
|
||||
OBJECT = "object",
|
||||
JSON = "json",
|
||||
}
|
||||
|
||||
export interface QueryDefinition {
|
||||
type: QueryTypes,
|
||||
displayName?: string,
|
||||
readable?: boolean,
|
||||
customisable?: boolean,
|
||||
fields?: object,
|
||||
urlDisplay?: boolean,
|
||||
}
|
||||
|
||||
export interface Integration {
|
||||
docs: string,
|
||||
plus?: boolean,
|
||||
description: string,
|
||||
friendlyName: string,
|
||||
datasource: {},
|
||||
query: {
|
||||
[key: string]: QueryDefinition,
|
||||
}
|
||||
}
|
||||
|
||||
export interface SearchFilters {
|
||||
allOr: boolean,
|
||||
string?: {
|
||||
[key: string]: string,
|
||||
},
|
||||
fuzzy?: {
|
||||
[key: string]: string,
|
||||
},
|
||||
range?: {
|
||||
[key: string]: {
|
||||
high: number | string,
|
||||
low: number | string,
|
||||
},
|
||||
},
|
||||
equal?: {
|
||||
[key: string]: any,
|
||||
},
|
||||
notEqual?: {
|
||||
[key: string]: any,
|
||||
},
|
||||
empty?: {
|
||||
[key: string]: any,
|
||||
},
|
||||
notEmpty?: {
|
||||
[key: string]: any,
|
||||
},
|
||||
}
|
||||
|
||||
export interface QueryJson {
|
||||
endpoint: {
|
||||
datasourceId: string,
|
||||
entityId: string,
|
||||
operation: Operation,
|
||||
},
|
||||
resource: {
|
||||
fields: string[],
|
||||
},
|
||||
filters?: SearchFilters,
|
||||
sort?: {
|
||||
[key: string]: SortDirection,
|
||||
},
|
||||
paginate?: {
|
||||
limit: number,
|
||||
page: string | number,
|
||||
},
|
||||
body?: object,
|
||||
extra: {
|
||||
idFilter?: SearchFilters,
|
||||
},
|
||||
}
|
||||
|
||||
export interface QueryOptions {
|
||||
disableReturning?: boolean,
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
const { DataSourceOperation, SortDirection } = require("../../constants")
|
||||
|
||||
import { Knex, knex } from "knex"
|
||||
const BASE_LIMIT = 5000
|
||||
import { QueryJson, SearchFilters, QueryOptions, SortDirection, Operation } from "./definitions"
|
||||
|
||||
function addFilters(query, filters) {
|
||||
function iterate(structure, fn) {
|
||||
|
||||
function addFilters(query: any, filters: SearchFilters | undefined): Knex.QueryBuilder {
|
||||
function iterate(structure: { [key: string]: any }, fn: (key: string, value: any) => void) {
|
||||
for (let [key, value] of Object.entries(structure)) {
|
||||
fn(key, value)
|
||||
}
|
||||
|
@ -12,7 +13,7 @@ function addFilters(query, filters) {
|
|||
return query
|
||||
}
|
||||
// if all or specified in filters, then everything is an or
|
||||
const allOr = !!filters.allOr
|
||||
const allOr = filters.allOr
|
||||
if (filters.string) {
|
||||
iterate(filters.string, (key, value) => {
|
||||
const fnc = allOr ? "orWhere" : "where"
|
||||
|
@ -55,7 +56,7 @@ function addFilters(query, filters) {
|
|||
return query
|
||||
}
|
||||
|
||||
function buildCreate(knex, json, opts) {
|
||||
function buildCreate(knex: Knex, json: QueryJson, opts: QueryOptions) {
|
||||
const { endpoint, body } = json
|
||||
let query = knex(endpoint.entityId)
|
||||
// mysql can't use returning
|
||||
|
@ -66,9 +67,9 @@ function buildCreate(knex, json, opts) {
|
|||
}
|
||||
}
|
||||
|
||||
function buildRead(knex, json, limit) {
|
||||
function buildRead(knex: Knex, json: QueryJson, limit: number) {
|
||||
let { endpoint, resource, filters, sort, paginate } = json
|
||||
let query = knex(endpoint.entityId)
|
||||
let query: Knex.QueryBuilder = knex(endpoint.entityId)
|
||||
// select all if not specified
|
||||
if (!resource) {
|
||||
resource = { fields: [] }
|
||||
|
@ -90,6 +91,7 @@ function buildRead(knex, json, limit) {
|
|||
}
|
||||
// handle pagination
|
||||
if (paginate && paginate.page && paginate.limit) {
|
||||
// @ts-ignore
|
||||
const page = paginate.page <= 1 ? 0 : paginate.page - 1
|
||||
const offset = page * paginate.limit
|
||||
query = query.offset(offset).limit(paginate.limit)
|
||||
|
@ -101,7 +103,7 @@ function buildRead(knex, json, limit) {
|
|||
return query
|
||||
}
|
||||
|
||||
function buildUpdate(knex, json, opts) {
|
||||
function buildUpdate(knex: Knex, json: QueryJson, opts: QueryOptions) {
|
||||
const { endpoint, body, filters } = json
|
||||
let query = knex(endpoint.entityId)
|
||||
query = addFilters(query, filters)
|
||||
|
@ -113,7 +115,7 @@ function buildUpdate(knex, json, opts) {
|
|||
}
|
||||
}
|
||||
|
||||
function buildDelete(knex, json, opts) {
|
||||
function buildDelete(knex: Knex, json: QueryJson, opts: QueryOptions) {
|
||||
const { endpoint, filters } = json
|
||||
let query = knex(endpoint.entityId)
|
||||
query = addFilters(query, filters)
|
||||
|
@ -126,20 +128,19 @@ function buildDelete(knex, json, opts) {
|
|||
}
|
||||
|
||||
class SqlQueryBuilder {
|
||||
private readonly client: any
|
||||
private readonly limit: number
|
||||
// pass through client to get flavour of SQL
|
||||
constructor(client, limit = BASE_LIMIT) {
|
||||
this._client = client
|
||||
this._limit = limit
|
||||
constructor(client: any, limit: number = BASE_LIMIT) {
|
||||
this.client = client
|
||||
this.limit = limit
|
||||
}
|
||||
|
||||
/**
|
||||
* @param json the input JSON structure from which an SQL query will be built.
|
||||
* @return {string} the operation that was found in the JSON.
|
||||
*/
|
||||
_operation(json) {
|
||||
if (!json || !json.endpoint) {
|
||||
return ""
|
||||
}
|
||||
_operation(json: QueryJson): Operation {
|
||||
return json.endpoint.operation
|
||||
}
|
||||
|
||||
|
@ -149,21 +150,21 @@ class SqlQueryBuilder {
|
|||
* which for the sake of mySQL stops adding the returning statement to inserts, updates and deletes.
|
||||
* @return {{ sql: string, bindings: object }} the query ready to be passed to the driver.
|
||||
*/
|
||||
_query(json, opts = {}) {
|
||||
const knex = require("knex")({ client: this._client })
|
||||
_query(json: QueryJson, opts: QueryOptions = {}) {
|
||||
const client = knex({ client: this.client })
|
||||
let query
|
||||
switch (this._operation(json)) {
|
||||
case DataSourceOperation.CREATE:
|
||||
query = buildCreate(knex, json, opts)
|
||||
case Operation.CREATE:
|
||||
query = buildCreate(client, json, opts)
|
||||
break
|
||||
case DataSourceOperation.READ:
|
||||
query = buildRead(knex, json, this._limit, opts)
|
||||
case Operation.READ:
|
||||
query = buildRead(client, json, this.limit)
|
||||
break
|
||||
case DataSourceOperation.UPDATE:
|
||||
query = buildUpdate(knex, json, opts)
|
||||
case Operation.UPDATE:
|
||||
query = buildUpdate(client, json, opts)
|
||||
break
|
||||
case DataSourceOperation.DELETE:
|
||||
query = buildDelete(knex, json, opts)
|
||||
case Operation.DELETE:
|
||||
query = buildDelete(client, json, opts)
|
||||
break
|
||||
default:
|
||||
throw `Operation type is not supported by SQL query builder`
|
|
@ -1,95 +0,0 @@
|
|||
const PouchDB = require("pouchdb")
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://docs.couchdb.org/en/stable/",
|
||||
friendlyName: "CouchDB",
|
||||
description:
|
||||
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
|
||||
datasource: {
|
||||
url: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
default: "http://localhost:5984",
|
||||
},
|
||||
database: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QUERY_TYPES.JSON,
|
||||
},
|
||||
read: {
|
||||
type: QUERY_TYPES.JSON,
|
||||
},
|
||||
update: {
|
||||
type: QUERY_TYPES.JSON,
|
||||
},
|
||||
delete: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
fields: {
|
||||
id: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class CouchDBIntegration {
|
||||
constructor(config) {
|
||||
this.config = config
|
||||
this.client = new PouchDB(`${config.url}/${config.database}`)
|
||||
}
|
||||
|
||||
async create(query) {
|
||||
try {
|
||||
const result = await this.client.post(query.json)
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error("Error writing to couchDB", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async read(query) {
|
||||
try {
|
||||
const result = await this.client.allDocs({
|
||||
include_docs: true,
|
||||
...query.json,
|
||||
})
|
||||
return result.rows.map(row => row.doc)
|
||||
} catch (err) {
|
||||
console.error("Error querying couchDB", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async update(query) {
|
||||
try {
|
||||
const result = await this.client.put(query.json)
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error("Error updating couchDB document", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async delete(query) {
|
||||
try {
|
||||
const result = await this.client.remove(query.id)
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error("Error deleting couchDB document", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: CouchDBIntegration,
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
import { Integration, DatasourceFieldTypes, QueryTypes } from "./base/definitions"
|
||||
|
||||
module CouchDBModule {
|
||||
const PouchDB = require("pouchdb")
|
||||
|
||||
interface CouchDBConfig {
|
||||
url: string
|
||||
database: string
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://docs.couchdb.org/en/stable/",
|
||||
friendlyName: "CouchDB",
|
||||
description:
|
||||
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
|
||||
datasource: {
|
||||
url: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
default: "http://localhost:5984",
|
||||
},
|
||||
database: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QueryTypes.JSON,
|
||||
},
|
||||
read: {
|
||||
type: QueryTypes.JSON,
|
||||
},
|
||||
update: {
|
||||
type: QueryTypes.JSON,
|
||||
},
|
||||
delete: {
|
||||
type: QueryTypes.FIELDS,
|
||||
fields: {
|
||||
id: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class CouchDBIntegration {
|
||||
private config: CouchDBConfig
|
||||
private client: any
|
||||
|
||||
constructor(config: CouchDBConfig) {
|
||||
this.config = config
|
||||
this.client = new PouchDB(`${config.url}/${config.database}`)
|
||||
}
|
||||
|
||||
async create(query: { json: object }) {
|
||||
try {
|
||||
return this.client.post(query.json)
|
||||
} catch (err) {
|
||||
console.error("Error writing to couchDB", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async read(query: { json: object }) {
|
||||
try {
|
||||
const result = await this.client.allDocs({
|
||||
include_docs: true,
|
||||
...query.json,
|
||||
})
|
||||
return result.rows.map((row: { doc: object }) => row.doc)
|
||||
} catch (err) {
|
||||
console.error("Error querying couchDB", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async update(query: { json: object }) {
|
||||
try {
|
||||
return this.client.put(query.json)
|
||||
} catch (err) {
|
||||
console.error("Error updating couchDB document", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async delete(query: { id: string }) {
|
||||
try {
|
||||
return await this.client.remove(query.id)
|
||||
} catch (err) {
|
||||
console.error("Error deleting couchDB document", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: CouchDBIntegration,
|
||||
}
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
const AWS = require("aws-sdk")
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration")
|
||||
const { AWS_REGION } = require("../db/dynamoClient")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://github.com/dabit3/dynamodb-documentclient-cheat-sheet",
|
||||
description:
|
||||
"Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.",
|
||||
friendlyName: "DynamoDB",
|
||||
datasource: {
|
||||
region: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
default: "us-east-1",
|
||||
},
|
||||
accessKeyId: {
|
||||
type: FIELD_TYPES.PASSWORD,
|
||||
required: true,
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: FIELD_TYPES.PASSWORD,
|
||||
required: true,
|
||||
},
|
||||
endpoint: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: false,
|
||||
default: "https://dynamodb.us-east-1.amazonaws.com",
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
readable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
},
|
||||
},
|
||||
scan: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
readable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
},
|
||||
},
|
||||
get: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
readable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class DynamoDBIntegration {
|
||||
constructor(config) {
|
||||
this.config = config
|
||||
this.connect()
|
||||
let options = {
|
||||
correctClockSkew: true,
|
||||
}
|
||||
if (config.endpoint) {
|
||||
options.endpoint = config.endpoint
|
||||
}
|
||||
this.client = new AWS.DynamoDB.DocumentClient({
|
||||
correctClockSkew: true,
|
||||
})
|
||||
}
|
||||
|
||||
end() {
|
||||
this.disconnect()
|
||||
}
|
||||
|
||||
connect() {
|
||||
AWS.config.update(this.config)
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
AWS.config.update({
|
||||
secretAccessKey: undefined,
|
||||
accessKeyId: undefined,
|
||||
region: AWS_REGION,
|
||||
})
|
||||
}
|
||||
|
||||
async create(query) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
return this.client.put(params).promise()
|
||||
}
|
||||
|
||||
async read(query) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
if (query.index) {
|
||||
params.IndexName = query.index
|
||||
}
|
||||
const response = await this.client.query(params).promise()
|
||||
if (response.Items) {
|
||||
return response.Items
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async scan(query) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
if (query.index) {
|
||||
params.IndexName = query.index
|
||||
}
|
||||
const response = await this.client.scan(params).promise()
|
||||
if (response.Items) {
|
||||
return response.Items
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async get(query) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
return this.client.get(params).promise()
|
||||
}
|
||||
|
||||
async update(query) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
return this.client.update(params).promise()
|
||||
}
|
||||
|
||||
async delete(query) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
return this.client.delete(params).promise()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: DynamoDBIntegration,
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
import { Integration, DatasourceFieldTypes, QueryTypes } from "./base/definitions"
|
||||
|
||||
module DynamoModule {
|
||||
const AWS = require("aws-sdk")
|
||||
const { AWS_REGION } = require("../db/dynamoClient")
|
||||
|
||||
interface DynamoDBConfig {
|
||||
region: string
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
endpoint: string
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://github.com/dabit3/dynamodb-documentclient-cheat-sheet",
|
||||
description:
|
||||
"Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.",
|
||||
friendlyName: "DynamoDB",
|
||||
datasource: {
|
||||
region: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
default: "us-east-1",
|
||||
},
|
||||
accessKeyId: {
|
||||
type: DatasourceFieldTypes.PASSWORD,
|
||||
required: true,
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: DatasourceFieldTypes.PASSWORD,
|
||||
required: true,
|
||||
},
|
||||
endpoint: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: false,
|
||||
default: "https://dynamodb.us-east-1.amazonaws.com",
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
readable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
},
|
||||
},
|
||||
scan: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
readable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
},
|
||||
},
|
||||
get: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
readable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
table: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class DynamoDBIntegration {
|
||||
private config: DynamoDBConfig
|
||||
private client: any
|
||||
|
||||
constructor(config: DynamoDBConfig) {
|
||||
this.config = config
|
||||
this.connect()
|
||||
let options = {
|
||||
correctClockSkew: true,
|
||||
endpoint: config.endpoint ? config.endpoint : undefined,
|
||||
}
|
||||
this.client = new AWS.DynamoDB.DocumentClient(options)
|
||||
}
|
||||
|
||||
end() {
|
||||
this.disconnect()
|
||||
}
|
||||
|
||||
connect() {
|
||||
AWS.config.update(this.config)
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
AWS.config.update({
|
||||
secretAccessKey: undefined,
|
||||
accessKeyId: undefined,
|
||||
region: AWS_REGION,
|
||||
})
|
||||
}
|
||||
|
||||
async create(query: { table: string; json: object }) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
return this.client.put(params).promise()
|
||||
}
|
||||
|
||||
async read(query: { table: string; json: object; index: null | string }) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
IndexName: query.index ? query.index : undefined,
|
||||
...query.json,
|
||||
}
|
||||
if (query.index) {
|
||||
const response = await this.client.query(params).promise()
|
||||
if (response.Items) {
|
||||
return response.Items
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
async scan(query: { table: string; json: object; index: null | string }) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
IndexName: query.index ? query.index : undefined,
|
||||
...query.json,
|
||||
}
|
||||
const response = await this.client.scan(params).promise()
|
||||
if (response.Items) {
|
||||
return response.Items
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async get(query: { table: string; json: object }) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
return this.client.get(params).promise()
|
||||
}
|
||||
|
||||
async update(query: { table: string; json: object }) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
return this.client.update(params).promise()
|
||||
}
|
||||
|
||||
async delete(query: { table: string; json: object }) {
|
||||
const params = {
|
||||
TableName: query.table,
|
||||
...query.json,
|
||||
}
|
||||
return this.client.delete(params).promise()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: DynamoDBIntegration,
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
const { Client } = require("@elastic/elasticsearch")
|
||||
const { QUERY_TYPES, FIELD_TYPES } = require("./Integration")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html",
|
||||
description:
|
||||
"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",
|
||||
datasource: {
|
||||
url: {
|
||||
type: "string",
|
||||
required: true,
|
||||
default: "http://localhost:9200",
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
index: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
index: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
id: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
fields: {
|
||||
index: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
id: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class ElasticSearchIntegration {
|
||||
constructor(config) {
|
||||
this.config = config
|
||||
this.client = new Client({ node: config.url })
|
||||
}
|
||||
|
||||
async create(query) {
|
||||
const { index, json } = query
|
||||
|
||||
try {
|
||||
const result = await this.client.index({
|
||||
index,
|
||||
body: json,
|
||||
})
|
||||
return result.body
|
||||
} catch (err) {
|
||||
console.error("Error writing to elasticsearch", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async read(query) {
|
||||
const { index, json } = query
|
||||
try {
|
||||
const result = await this.client.search({
|
||||
index: index,
|
||||
body: json,
|
||||
})
|
||||
return result.body.hits.hits.map(({ _source }) => _source)
|
||||
} catch (err) {
|
||||
console.error("Error querying elasticsearch", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async update(query) {
|
||||
const { id, index, json } = query
|
||||
try {
|
||||
const result = await this.client.update({
|
||||
id,
|
||||
index,
|
||||
body: json,
|
||||
})
|
||||
return result.body
|
||||
} catch (err) {
|
||||
console.error("Error querying elasticsearch", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async delete(query) {
|
||||
try {
|
||||
const result = await this.client.delete(query)
|
||||
return result.body
|
||||
} catch (err) {
|
||||
console.error("Error deleting from elasticsearch", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: ElasticSearchIntegration,
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
import { Integration, DatasourceFieldTypes, QueryTypes } from "./base/definitions"
|
||||
|
||||
module ElasticsearchModule {
|
||||
const { Client } = require("@elastic/elasticsearch")
|
||||
|
||||
interface ElasticsearchConfig {
|
||||
url: string
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html",
|
||||
description:
|
||||
"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",
|
||||
datasource: {
|
||||
url: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
default: "http://localhost:9200",
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
index: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
index: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
type: QueryTypes.FIELDS,
|
||||
customisable: true,
|
||||
fields: {
|
||||
id: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
type: QueryTypes.FIELDS,
|
||||
fields: {
|
||||
index: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
id: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class ElasticSearchIntegration {
|
||||
private config: ElasticsearchConfig
|
||||
private client: any
|
||||
|
||||
constructor(config: ElasticsearchConfig) {
|
||||
this.config = config
|
||||
this.client = new Client({ node: config.url })
|
||||
}
|
||||
|
||||
async create(query: { index: string; json: object }) {
|
||||
const { index, json } = query
|
||||
|
||||
try {
|
||||
const result = await this.client.index({
|
||||
index,
|
||||
body: json,
|
||||
})
|
||||
return result.body
|
||||
} catch (err) {
|
||||
console.error("Error writing to elasticsearch", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async read(query: { index: string; json: object }) {
|
||||
const { index, json } = query
|
||||
try {
|
||||
const result = await this.client.search({
|
||||
index: index,
|
||||
body: json,
|
||||
})
|
||||
return result.body.hits.hits.map(({ _source }: any) => _source)
|
||||
} catch (err) {
|
||||
console.error("Error querying elasticsearch", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async update(query: { id: string; index: string; json: object }) {
|
||||
const { id, index, json } = query
|
||||
try {
|
||||
const result = await this.client.update({
|
||||
id,
|
||||
index,
|
||||
body: json,
|
||||
})
|
||||
return result.body
|
||||
} catch (err) {
|
||||
console.error("Error querying elasticsearch", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async delete(query: object) {
|
||||
try {
|
||||
const result = await this.client.delete(query)
|
||||
return result.body
|
||||
} catch (err) {
|
||||
console.error("Error deleting from elasticsearch", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: ElasticSearchIntegration,
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
const sqlServer = require("mssql")
|
||||
const { FIELD_TYPES } = require("./Integration")
|
||||
const Sql = require("./base/sql")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://github.com/tediousjs/node-mssql",
|
||||
description:
|
||||
"Microsoft SQL Server is a relational database management system developed by Microsoft. ",
|
||||
friendlyName: "MS SQL Server",
|
||||
datasource: {
|
||||
user: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
default: "localhost",
|
||||
},
|
||||
password: {
|
||||
type: FIELD_TYPES.PASSWORD,
|
||||
required: true,
|
||||
},
|
||||
server: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "localhost",
|
||||
},
|
||||
port: {
|
||||
type: FIELD_TYPES.NUMBER,
|
||||
required: false,
|
||||
default: 1433,
|
||||
},
|
||||
database: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "root",
|
||||
},
|
||||
encrypt: {
|
||||
type: FIELD_TYPES.BOOLEAN,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: "sql",
|
||||
},
|
||||
read: {
|
||||
type: "sql",
|
||||
},
|
||||
update: {
|
||||
type: "sql",
|
||||
},
|
||||
delete: {
|
||||
type: "sql",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
async function internalQuery(client, query) {
|
||||
const sql = typeof query === "string" ? query : query.sql
|
||||
const bindings = typeof query === "string" ? {} : query.bindings
|
||||
try {
|
||||
return await client.query(sql, bindings)
|
||||
} catch (err) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
class SqlServerIntegration extends Sql {
|
||||
static pool
|
||||
|
||||
constructor(config) {
|
||||
super("mssql")
|
||||
this.config = config
|
||||
this.config.options = {
|
||||
encrypt: this.config.encrypt,
|
||||
}
|
||||
delete this.config.encrypt
|
||||
if (!this.pool) {
|
||||
this.pool = new sqlServer.ConnectionPool(this.config)
|
||||
}
|
||||
}
|
||||
|
||||
async connect() {
|
||||
try {
|
||||
const client = await this.pool.connect()
|
||||
this.client = client.request()
|
||||
} catch (err) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async read(query) {
|
||||
await this.connect()
|
||||
const response = await internalQuery(this.client, query)
|
||||
return response.recordset
|
||||
}
|
||||
|
||||
async create(query) {
|
||||
await this.connect()
|
||||
const response = await internalQuery(this.client, query)
|
||||
return response.recordset || [{ created: true }]
|
||||
}
|
||||
|
||||
async update(query) {
|
||||
await this.connect()
|
||||
const response = await internalQuery(this.client, query)
|
||||
return response.recordset || [{ updated: true }]
|
||||
}
|
||||
|
||||
async delete(query) {
|
||||
await this.connect()
|
||||
const response = await internalQuery(this.client, query)
|
||||
return response.recordset || [{ deleted: true }]
|
||||
}
|
||||
|
||||
async query(json) {
|
||||
const operation = this._operation(json).toLowerCase()
|
||||
const input = this._query(json)
|
||||
const response = await internalQuery(this.client, input)
|
||||
return response.recordset ? response.recordset : [{ [operation]: true }]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: SqlServerIntegration,
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
import {Integration, DatasourceFieldTypes, QueryTypes, QueryJson} from "./base/definitions"
|
||||
|
||||
module MSSQLModule {
|
||||
const sqlServer = require("mssql")
|
||||
const Sql = require("./base/sql")
|
||||
|
||||
interface MSSQLConfig {
|
||||
user: string
|
||||
password: string
|
||||
server: string
|
||||
port: number
|
||||
database: string
|
||||
encrypt?: boolean
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://github.com/tediousjs/node-mssql",
|
||||
description:
|
||||
"Microsoft SQL Server is a relational database management system developed by Microsoft. ",
|
||||
friendlyName: "MS SQL Server",
|
||||
datasource: {
|
||||
user: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
default: "localhost",
|
||||
},
|
||||
password: {
|
||||
type: DatasourceFieldTypes.PASSWORD,
|
||||
required: true,
|
||||
},
|
||||
server: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "localhost",
|
||||
},
|
||||
port: {
|
||||
type: DatasourceFieldTypes.NUMBER,
|
||||
required: false,
|
||||
default: 1433,
|
||||
},
|
||||
database: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "root",
|
||||
},
|
||||
encrypt: {
|
||||
type: DatasourceFieldTypes.BOOLEAN,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
read: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
update: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
delete: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
async function internalQuery(
|
||||
client: any,
|
||||
query: { sql: string; bindings?: object }
|
||||
) {
|
||||
try {
|
||||
return await client.query(query.sql, query.bindings || {})
|
||||
} catch (err) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
class SqlServerIntegration extends Sql {
|
||||
private readonly config: MSSQLConfig
|
||||
static pool: any
|
||||
|
||||
constructor(config: MSSQLConfig) {
|
||||
super("mssql")
|
||||
this.config = config
|
||||
const clientCfg = {
|
||||
...this.config,
|
||||
options: {
|
||||
encrypt: this.config.encrypt,
|
||||
},
|
||||
}
|
||||
delete clientCfg.encrypt
|
||||
if (!this.pool) {
|
||||
this.pool = new sqlServer.ConnectionPool(clientCfg)
|
||||
}
|
||||
}
|
||||
|
||||
async connect() {
|
||||
try {
|
||||
const client = await this.pool.connect()
|
||||
this.client = client.request()
|
||||
} catch (err) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async read(query: string) {
|
||||
await this.connect()
|
||||
const response = await internalQuery(this.client, { sql: query })
|
||||
return response.recordset
|
||||
}
|
||||
|
||||
async create(query: string) {
|
||||
await this.connect()
|
||||
const response = await internalQuery(this.client, { sql: query })
|
||||
return response.recordset || [{ created: true }]
|
||||
}
|
||||
|
||||
async update(query: string) {
|
||||
await this.connect()
|
||||
const response = await internalQuery(this.client, { sql: query })
|
||||
return response.recordset || [{ updated: true }]
|
||||
}
|
||||
|
||||
async delete(query: string) {
|
||||
await this.connect()
|
||||
const response = await internalQuery(this.client, { sql: query })
|
||||
return response.recordset || [{ deleted: true }]
|
||||
}
|
||||
|
||||
async query(json: QueryJson) {
|
||||
const operation = this._operation(json).toLowerCase()
|
||||
const input = this._query(json)
|
||||
const response = await internalQuery(this.client, input)
|
||||
return response.recordset ? response.recordset : [{ [operation]: true }]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: SqlServerIntegration,
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
const { MongoClient } = require("mongodb")
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://github.com/mongodb/node-mongodb-native",
|
||||
friendlyName: "MongoDB",
|
||||
description:
|
||||
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
|
||||
datasource: {
|
||||
connectionString: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
default: "mongodb://localhost:27017",
|
||||
},
|
||||
db: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QUERY_TYPES.JSON,
|
||||
},
|
||||
read: {
|
||||
type: QUERY_TYPES.JSON,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class MongoIntegration {
|
||||
constructor(config) {
|
||||
this.config = config
|
||||
this.client = new MongoClient(config.connectionString)
|
||||
}
|
||||
|
||||
async connect() {
|
||||
return this.client.connect()
|
||||
}
|
||||
|
||||
async create(query) {
|
||||
try {
|
||||
await this.connect()
|
||||
const db = this.client.db(this.config.db)
|
||||
const collection = db.collection(this.config.collection)
|
||||
const result = await collection.insertOne(query.json)
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error("Error writing to mongodb", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async read(query) {
|
||||
try {
|
||||
await this.connect()
|
||||
const db = this.client.db(this.config.db)
|
||||
const collection = db.collection(this.config.collection)
|
||||
const result = await collection.find(query.json).toArray()
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error("Error querying mongodb", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: MongoIntegration,
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import { Integration, DatasourceFieldTypes, QueryTypes } from "./base/definitions"
|
||||
|
||||
module MongoDBModule {
|
||||
const { MongoClient } = require("mongodb")
|
||||
|
||||
interface MongoDBConfig {
|
||||
connectionString: string
|
||||
db: string
|
||||
collection: string
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://github.com/mongodb/node-mongodb-native",
|
||||
friendlyName: "MongoDB",
|
||||
description:
|
||||
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
|
||||
datasource: {
|
||||
connectionString: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
default: "mongodb://localhost:27017",
|
||||
},
|
||||
db: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QueryTypes.JSON,
|
||||
},
|
||||
read: {
|
||||
type: QueryTypes.JSON,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class MongoIntegration {
|
||||
private config: MongoDBConfig
|
||||
private client: any
|
||||
|
||||
constructor(config: MongoDBConfig) {
|
||||
this.config = config
|
||||
this.client = new MongoClient(config.connectionString)
|
||||
}
|
||||
|
||||
async connect() {
|
||||
return this.client.connect()
|
||||
}
|
||||
|
||||
async create(query: { json: object }) {
|
||||
try {
|
||||
await this.connect()
|
||||
const db = this.client.db(this.config.db)
|
||||
const collection = db.collection(this.config.collection)
|
||||
return collection.insertOne(query.json)
|
||||
} catch (err) {
|
||||
console.error("Error writing to mongodb", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async read(query: { json: object }) {
|
||||
try {
|
||||
await this.connect()
|
||||
const db = this.client.db(this.config.db)
|
||||
const collection = db.collection(this.config.collection)
|
||||
return collection.find(query.json).toArray()
|
||||
} catch (err) {
|
||||
console.error("Error querying mongodb", err)
|
||||
throw err
|
||||
} finally {
|
||||
await this.client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: MongoIntegration,
|
||||
}
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
const mysql = require("mysql")
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration")
|
||||
const Sql = require("./base/sql")
|
||||
const { buildExternalTableId, convertType } = require("./utils")
|
||||
const { FieldTypes } = require("../constants")
|
||||
const { Operation } = require("./base/constants")
|
||||
|
||||
const TYPE_MAP = {
|
||||
text: FieldTypes.LONGFORM,
|
||||
blob: FieldTypes.LONGFORM,
|
||||
enum: FieldTypes.STRING,
|
||||
varchar: FieldTypes.STRING,
|
||||
int: FieldTypes.NUMBER,
|
||||
numeric: FieldTypes.NUMBER,
|
||||
bigint: FieldTypes.NUMBER,
|
||||
mediumint: FieldTypes.NUMBER,
|
||||
decimal: FieldTypes.NUMBER,
|
||||
dec: FieldTypes.NUMBER,
|
||||
double: FieldTypes.NUMBER,
|
||||
real: FieldTypes.NUMBER,
|
||||
fixed: FieldTypes.NUMBER,
|
||||
smallint: FieldTypes.NUMBER,
|
||||
timestamp: FieldTypes.DATETIME,
|
||||
date: FieldTypes.DATETIME,
|
||||
datetime: FieldTypes.DATETIME,
|
||||
time: FieldTypes.DATETIME,
|
||||
tinyint: FieldTypes.BOOLEAN,
|
||||
json: FIELD_TYPES.JSON,
|
||||
}
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://github.com/mysqljs/mysql",
|
||||
plus: true,
|
||||
friendlyName: "MySQL",
|
||||
description:
|
||||
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
|
||||
datasource: {
|
||||
host: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "localhost",
|
||||
required: true,
|
||||
},
|
||||
port: {
|
||||
type: FIELD_TYPES.NUMBER,
|
||||
default: 3306,
|
||||
required: false,
|
||||
},
|
||||
user: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
password: {
|
||||
type: FIELD_TYPES.PASSWORD,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
database: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
required: true,
|
||||
},
|
||||
ssl: {
|
||||
type: FIELD_TYPES.OBJECT,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QUERY_TYPES.SQL,
|
||||
},
|
||||
read: {
|
||||
type: QUERY_TYPES.SQL,
|
||||
},
|
||||
update: {
|
||||
type: QUERY_TYPES.SQL,
|
||||
},
|
||||
delete: {
|
||||
type: QUERY_TYPES.SQL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function internalQuery(client, query, connect = true) {
|
||||
const sql = typeof query === "string" ? query : query.sql
|
||||
const bindings = typeof query === "string" ? {} : query.bindings
|
||||
// Node MySQL is callback based, so we must wrap our call in a promise
|
||||
return new Promise((resolve, reject) => {
|
||||
if (connect) {
|
||||
client.connect()
|
||||
}
|
||||
return client.query(sql, bindings, (error, results) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(results)
|
||||
}
|
||||
if (connect) {
|
||||
client.end()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
class MySQLIntegration extends Sql {
|
||||
constructor(config) {
|
||||
super("mysql")
|
||||
this.config = config
|
||||
if (config.ssl && Object.keys(config.ssl).length === 0) {
|
||||
delete config.ssl
|
||||
}
|
||||
this.client = mysql.createConnection(config)
|
||||
}
|
||||
|
||||
async buildSchema(datasourceId) {
|
||||
const tables = {}
|
||||
const database = this.config.database
|
||||
this.client.connect()
|
||||
|
||||
// get the tables first
|
||||
const tablesResp = await internalQuery(this.client, "SHOW TABLES;", false)
|
||||
const tableNames = tablesResp.map(obj => obj[`Tables_in_${database}`])
|
||||
for (let tableName of tableNames) {
|
||||
const primaryKeys = []
|
||||
const schema = {}
|
||||
const descResp = await internalQuery(
|
||||
this.client,
|
||||
`DESCRIBE ${tableName};`,
|
||||
false
|
||||
)
|
||||
for (let column of descResp) {
|
||||
const columnName = column.Field
|
||||
if (column.Key === "PRI") {
|
||||
primaryKeys.push(columnName)
|
||||
}
|
||||
const constraints = {}
|
||||
if (column.Null !== "YES") {
|
||||
constraints.required = true
|
||||
}
|
||||
schema[columnName] = {
|
||||
name: columnName,
|
||||
type: convertType(column.Type, TYPE_MAP),
|
||||
constraints,
|
||||
}
|
||||
}
|
||||
// for now just default to first column
|
||||
if (primaryKeys.length === 0) {
|
||||
primaryKeys.push(descResp[0].Field)
|
||||
}
|
||||
if (!tables[tableName]) {
|
||||
tables[tableName] = {
|
||||
_id: buildExternalTableId(datasourceId, tableName),
|
||||
primary: primaryKeys,
|
||||
name: tableName,
|
||||
schema,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.client.end()
|
||||
this.tables = tables
|
||||
}
|
||||
|
||||
async create(query) {
|
||||
const results = await internalQuery(this.client, query)
|
||||
return results.length ? results : [{ created: true }]
|
||||
}
|
||||
|
||||
read(query) {
|
||||
return internalQuery(this.client, query)
|
||||
}
|
||||
|
||||
async update(query) {
|
||||
const results = await internalQuery(this.client, query)
|
||||
return results.length ? results : [{ updated: true }]
|
||||
}
|
||||
|
||||
async delete(query) {
|
||||
const results = await internalQuery(this.client, query)
|
||||
return results.length ? results : [{ deleted: true }]
|
||||
}
|
||||
|
||||
async getReturningRow(json) {
|
||||
if (!json.extra.idFilter) {
|
||||
return {}
|
||||
}
|
||||
const input = this._query({
|
||||
endpoint: {
|
||||
...json.endpoint,
|
||||
operation: Operation.READ,
|
||||
},
|
||||
fields: [],
|
||||
filters: json.extra.idFilter,
|
||||
paginate: {
|
||||
limit: 1,
|
||||
},
|
||||
})
|
||||
return internalQuery(this.client, input, false)
|
||||
}
|
||||
|
||||
async query(json) {
|
||||
const operation = this._operation(json)
|
||||
this.client.connect()
|
||||
const input = this._query(json, { disableReturning: true })
|
||||
let row
|
||||
// need to manage returning, a feature mySQL can't do
|
||||
if (operation === Operation.DELETE) {
|
||||
row = this.getReturningRow(json)
|
||||
}
|
||||
const results = await internalQuery(this.client, input, false)
|
||||
// same as delete, manage returning
|
||||
if (operation === Operation.CREATE || operation === Operation.UPDATE) {
|
||||
row = this.getReturningRow(json)
|
||||
}
|
||||
this.client.end()
|
||||
if (operation !== Operation.READ) {
|
||||
return row
|
||||
}
|
||||
return results.length ? results : [{ [operation.toLowerCase()]: true }]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: MySQLIntegration,
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
import { Integration, DatasourceFieldTypes, QueryTypes, Operation, QueryJson } from "./base/definitions"
|
||||
|
||||
module MySQLModule {
|
||||
const mysql = require("mysql")
|
||||
const Sql = require("./base/sql")
|
||||
const { buildExternalTableId, convertType } = require("./utils")
|
||||
const { FieldTypes } = require("../constants")
|
||||
|
||||
interface MySQLConfig {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
database: string
|
||||
ssl?: object
|
||||
}
|
||||
|
||||
const TYPE_MAP = {
|
||||
text: FieldTypes.LONGFORM,
|
||||
blob: FieldTypes.LONGFORM,
|
||||
enum: FieldTypes.STRING,
|
||||
varchar: FieldTypes.STRING,
|
||||
int: FieldTypes.NUMBER,
|
||||
numeric: FieldTypes.NUMBER,
|
||||
bigint: FieldTypes.NUMBER,
|
||||
mediumint: FieldTypes.NUMBER,
|
||||
decimal: FieldTypes.NUMBER,
|
||||
dec: FieldTypes.NUMBER,
|
||||
double: FieldTypes.NUMBER,
|
||||
real: FieldTypes.NUMBER,
|
||||
fixed: FieldTypes.NUMBER,
|
||||
smallint: FieldTypes.NUMBER,
|
||||
timestamp: FieldTypes.DATETIME,
|
||||
date: FieldTypes.DATETIME,
|
||||
datetime: FieldTypes.DATETIME,
|
||||
time: FieldTypes.DATETIME,
|
||||
tinyint: FieldTypes.BOOLEAN,
|
||||
json: DatasourceFieldTypes.JSON,
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://github.com/mysqljs/mysql",
|
||||
plus: true,
|
||||
friendlyName: "MySQL",
|
||||
description:
|
||||
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
|
||||
datasource: {
|
||||
host: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "localhost",
|
||||
required: true,
|
||||
},
|
||||
port: {
|
||||
type: DatasourceFieldTypes.NUMBER,
|
||||
default: 3306,
|
||||
required: false,
|
||||
},
|
||||
user: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
password: {
|
||||
type: DatasourceFieldTypes.PASSWORD,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
database: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
ssl: {
|
||||
type: DatasourceFieldTypes.OBJECT,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
read: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
update: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
delete: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function internalQuery(
|
||||
client: any,
|
||||
query: { sql: string; bindings?: object },
|
||||
connect: boolean = true
|
||||
): Promise<any[]> {
|
||||
// Node MySQL is callback based, so we must wrap our call in a promise
|
||||
return new Promise((resolve, reject) => {
|
||||
if (connect) {
|
||||
client.connect()
|
||||
}
|
||||
return client.query(
|
||||
query.sql,
|
||||
query.bindings || {},
|
||||
(error: any, results: object[]) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(results)
|
||||
}
|
||||
if (connect) {
|
||||
client.end()
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
class MySQLIntegration extends Sql {
|
||||
private config: MySQLConfig
|
||||
private readonly client: any
|
||||
|
||||
constructor(config: MySQLConfig) {
|
||||
super("mysql")
|
||||
this.config = config
|
||||
if (config.ssl && Object.keys(config.ssl).length === 0) {
|
||||
delete config.ssl
|
||||
}
|
||||
this.client = mysql.createConnection(config)
|
||||
}
|
||||
|
||||
async buildSchema(datasourceId: string) {
|
||||
const tables: any = {}
|
||||
const database = this.config.database
|
||||
this.client.connect()
|
||||
|
||||
// get the tables first
|
||||
const tablesResp = await internalQuery(
|
||||
this.client,
|
||||
{ sql: "SHOW TABLES;" },
|
||||
false
|
||||
)
|
||||
const tableNames = tablesResp.map((obj: any) => obj[`Tables_in_${database}`])
|
||||
for (let tableName of tableNames) {
|
||||
const primaryKeys = []
|
||||
const schema: any = {}
|
||||
const descResp = await internalQuery(
|
||||
this.client,
|
||||
{ sql: `DESCRIBE ${tableName};` },
|
||||
false
|
||||
)
|
||||
for (let column of descResp) {
|
||||
const columnName = column.Field
|
||||
if (column.Key === "PRI") {
|
||||
primaryKeys.push(columnName)
|
||||
}
|
||||
const constraints = {
|
||||
required: column.Null !== "YES",
|
||||
}
|
||||
schema[columnName] = {
|
||||
name: columnName,
|
||||
type: convertType(column.Type, TYPE_MAP),
|
||||
constraints,
|
||||
}
|
||||
}
|
||||
// for now just default to first column
|
||||
if (primaryKeys.length === 0) {
|
||||
primaryKeys.push(descResp[0].Field)
|
||||
}
|
||||
if (!tables[tableName]) {
|
||||
tables[tableName] = {
|
||||
_id: buildExternalTableId(datasourceId, tableName),
|
||||
primary: primaryKeys,
|
||||
name: tableName,
|
||||
schema,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.client.end()
|
||||
this.tables = tables
|
||||
}
|
||||
|
||||
async create(query: string) {
|
||||
const results = await internalQuery(this.client, { sql: query })
|
||||
return results.length ? results : [{ created: true }]
|
||||
}
|
||||
|
||||
read(query: string) {
|
||||
return internalQuery(this.client, { sql: query })
|
||||
}
|
||||
|
||||
async update(query: string) {
|
||||
const results = await internalQuery(this.client, { sql: query })
|
||||
return results.length ? results : [{ updated: true }]
|
||||
}
|
||||
|
||||
async delete(query: string) {
|
||||
const results = await internalQuery(this.client, { sql: query })
|
||||
return results.length ? results : [{ deleted: true }]
|
||||
}
|
||||
|
||||
async getReturningRow(json: QueryJson) {
|
||||
if (!json.extra.idFilter) {
|
||||
return {}
|
||||
}
|
||||
const input = this._query({
|
||||
endpoint: {
|
||||
...json.endpoint,
|
||||
operation: Operation.READ,
|
||||
},
|
||||
fields: [],
|
||||
filters: json.extra.idFilter,
|
||||
paginate: {
|
||||
limit: 1,
|
||||
},
|
||||
})
|
||||
return internalQuery(this.client, input, false)
|
||||
}
|
||||
|
||||
async query(json: QueryJson) {
|
||||
const operation = this._operation(json)
|
||||
this.client.connect()
|
||||
const input = this._query(json, { disableReturning: true })
|
||||
let row
|
||||
// need to manage returning, a feature mySQL can't do
|
||||
if (operation === "awdawd") {
|
||||
row = this.getReturningRow(json)
|
||||
}
|
||||
const results = await internalQuery(this.client, input, false)
|
||||
// same as delete, manage returning
|
||||
if (operation === Operation.CREATE || operation === Operation.UPDATE) {
|
||||
row = this.getReturningRow(json)
|
||||
}
|
||||
this.client.end()
|
||||
if (operation !== Operation.READ) {
|
||||
return row
|
||||
}
|
||||
return results.length ? results : [{ [operation.toLowerCase()]: true }]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: MySQLIntegration,
|
||||
}
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
const { Pool } = require("pg")
|
||||
const { FIELD_TYPES } = require("./Integration")
|
||||
const Sql = require("./base/sql")
|
||||
const { FieldTypes } = require("../constants")
|
||||
const { buildExternalTableId, convertType } = require("./utils")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://node-postgres.com",
|
||||
plus: true,
|
||||
friendlyName: "PostgreSQL",
|
||||
description:
|
||||
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.",
|
||||
datasource: {
|
||||
host: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "localhost",
|
||||
required: true,
|
||||
},
|
||||
port: {
|
||||
type: FIELD_TYPES.NUMBER,
|
||||
required: true,
|
||||
default: 5432,
|
||||
},
|
||||
database: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "postgres",
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
password: {
|
||||
type: FIELD_TYPES.PASSWORD,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
ssl: {
|
||||
type: FIELD_TYPES.BOOLEAN,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: "sql",
|
||||
},
|
||||
read: {
|
||||
type: "sql",
|
||||
},
|
||||
update: {
|
||||
type: "sql",
|
||||
},
|
||||
delete: {
|
||||
type: "sql",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const TYPE_MAP = {
|
||||
text: FieldTypes.LONGFORM,
|
||||
varchar: FieldTypes.STRING,
|
||||
integer: FieldTypes.NUMBER,
|
||||
bigint: FieldTypes.NUMBER,
|
||||
decimal: FieldTypes.NUMBER,
|
||||
smallint: FieldTypes.NUMBER,
|
||||
timestamp: FieldTypes.DATETIME,
|
||||
time: FieldTypes.DATETIME,
|
||||
boolean: FieldTypes.BOOLEAN,
|
||||
json: FIELD_TYPES.JSON,
|
||||
}
|
||||
|
||||
async function internalQuery(client, query) {
|
||||
const sql = typeof query === "string" ? query : query.sql
|
||||
const bindings = typeof query === "string" ? {} : query.bindings
|
||||
try {
|
||||
return await client.query(sql, bindings)
|
||||
} catch (err) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
class PostgresIntegration extends Sql {
|
||||
static pool
|
||||
|
||||
COLUMNS_SQL =
|
||||
"select * from information_schema.columns where table_schema = 'public'"
|
||||
|
||||
PRIMARY_KEYS_SQL = `
|
||||
select tc.table_schema, tc.table_name, kc.column_name as primary_key
|
||||
from information_schema.table_constraints tc
|
||||
join
|
||||
information_schema.key_column_usage kc on kc.table_name = tc.table_name
|
||||
and kc.table_schema = tc.table_schema
|
||||
and kc.constraint_name = tc.constraint_name
|
||||
where tc.constraint_type = 'PRIMARY KEY';
|
||||
`
|
||||
|
||||
constructor(config) {
|
||||
super("pg")
|
||||
this.config = config
|
||||
if (this.config.ssl) {
|
||||
this.config.ssl = {
|
||||
rejectUnauthorized: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.pool) {
|
||||
this.pool = new Pool(this.config)
|
||||
}
|
||||
|
||||
this.client = this.pool
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the tables from the postgres table and assigns them to the datasource.
|
||||
* @param {*} datasourceId - datasourceId to fetch
|
||||
*/
|
||||
async buildSchema(datasourceId) {
|
||||
let tableKeys = {}
|
||||
try {
|
||||
const primaryKeysResponse = await this.client.query(this.PRIMARY_KEYS_SQL)
|
||||
for (let table of primaryKeysResponse.rows) {
|
||||
const tableName = table.table_name
|
||||
if (!tableKeys[tableName]) {
|
||||
tableKeys[tableName] = []
|
||||
}
|
||||
tableKeys[tableName].push(table.column_name || table.primary_key)
|
||||
}
|
||||
} catch (err) {
|
||||
tableKeys = {}
|
||||
}
|
||||
|
||||
const columnsResponse = await this.client.query(this.COLUMNS_SQL)
|
||||
const tables = {}
|
||||
|
||||
for (let column of columnsResponse.rows) {
|
||||
const tableName = column.table_name
|
||||
const columnName = column.column_name
|
||||
|
||||
// table key doesn't exist yet
|
||||
if (!tables[tableName]) {
|
||||
tables[tableName] = {
|
||||
_id: buildExternalTableId(datasourceId, tableName),
|
||||
primary: tableKeys[tableName] || ["id"],
|
||||
name: tableName,
|
||||
schema: {},
|
||||
}
|
||||
}
|
||||
|
||||
tables[tableName].schema[columnName] = {
|
||||
name: columnName,
|
||||
type: convertType(column.data_type, TYPE_MAP),
|
||||
}
|
||||
}
|
||||
this.tables = tables
|
||||
}
|
||||
|
||||
async create(sql) {
|
||||
const response = await internalQuery(this.client, sql)
|
||||
return response.rows.length ? response.rows : [{ created: true }]
|
||||
}
|
||||
|
||||
async read(sql) {
|
||||
const response = await internalQuery(this.client, sql)
|
||||
return response.rows
|
||||
}
|
||||
|
||||
async update(sql) {
|
||||
const response = await internalQuery(this.client, sql)
|
||||
return response.rows.length ? response.rows : [{ updated: true }]
|
||||
}
|
||||
|
||||
async delete(sql) {
|
||||
const response = await internalQuery(this.client, sql)
|
||||
return response.rows.length ? response.rows : [{ deleted: true }]
|
||||
}
|
||||
|
||||
async query(json) {
|
||||
const operation = this._operation(json).toLowerCase()
|
||||
const input = this._query(json)
|
||||
const response = await internalQuery(this.client, input)
|
||||
return response.rows.length ? response.rows : [{ [operation]: true }]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: PostgresIntegration,
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
import { Integration, DatasourceFieldTypes, QueryTypes, QueryJson } from "./base/definitions"
|
||||
import { Table } from "../constants/definitions"
|
||||
|
||||
module PostgresModule {
|
||||
const { Pool } = require("pg")
|
||||
const Sql = require("./base/sql")
|
||||
const { FieldTypes } = require("../constants")
|
||||
const { buildExternalTableId, convertType } = require("./utils")
|
||||
|
||||
interface PostgresConfig {
|
||||
host: string,
|
||||
port: number,
|
||||
database: string,
|
||||
user: string,
|
||||
password: string,
|
||||
ssl?: boolean,
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://node-postgres.com",
|
||||
plus: true,
|
||||
friendlyName: "PostgreSQL",
|
||||
description:
|
||||
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.",
|
||||
datasource: {
|
||||
host: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "localhost",
|
||||
required: true,
|
||||
},
|
||||
port: {
|
||||
type: DatasourceFieldTypes.NUMBER,
|
||||
required: true,
|
||||
default: 5432,
|
||||
},
|
||||
database: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "postgres",
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
password: {
|
||||
type: DatasourceFieldTypes.PASSWORD,
|
||||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
ssl: {
|
||||
type: DatasourceFieldTypes.BOOLEAN,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
read: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
update: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
delete: {
|
||||
type: QueryTypes.SQL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const TYPE_MAP = {
|
||||
text: FieldTypes.LONGFORM,
|
||||
varchar: FieldTypes.STRING,
|
||||
integer: FieldTypes.NUMBER,
|
||||
bigint: FieldTypes.NUMBER,
|
||||
decimal: FieldTypes.NUMBER,
|
||||
smallint: FieldTypes.NUMBER,
|
||||
timestamp: FieldTypes.DATETIME,
|
||||
time: FieldTypes.DATETIME,
|
||||
boolean: FieldTypes.BOOLEAN,
|
||||
json: FieldTypes.JSON,
|
||||
}
|
||||
|
||||
async function internalQuery(client: any, query: { sql: string, bindings?: object }) {
|
||||
try {
|
||||
return await client.query(query.sql, query.bindings || {})
|
||||
} catch (err) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
class PostgresIntegration extends Sql {
|
||||
static pool: any
|
||||
private readonly client: any
|
||||
private readonly config: PostgresConfig
|
||||
|
||||
COLUMNS_SQL =
|
||||
"select * from information_schema.columns where table_schema = 'public'"
|
||||
|
||||
PRIMARY_KEYS_SQL = `
|
||||
select tc.table_schema, tc.table_name, kc.column_name as primary_key
|
||||
from information_schema.table_constraints tc
|
||||
join
|
||||
information_schema.key_column_usage kc on kc.table_name = tc.table_name
|
||||
and kc.table_schema = tc.table_schema
|
||||
and kc.constraint_name = tc.constraint_name
|
||||
where tc.constraint_type = 'PRIMARY KEY';
|
||||
`
|
||||
|
||||
constructor(config: PostgresConfig) {
|
||||
super("pg")
|
||||
this.config = config
|
||||
|
||||
let newConfig = {
|
||||
...this.config,
|
||||
ssl: this.config.ssl ? { rejectUnauthorized: true } : undefined,
|
||||
}
|
||||
if (!this.pool) {
|
||||
this.pool = new Pool(newConfig)
|
||||
}
|
||||
|
||||
this.client = this.pool
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the tables from the postgres table and assigns them to the datasource.
|
||||
* @param {*} datasourceId - datasourceId to fetch
|
||||
*/
|
||||
async buildSchema(datasourceId: string) {
|
||||
let tableKeys: { [key: string]: string[] } = {}
|
||||
try {
|
||||
const primaryKeysResponse = await this.client.query(this.PRIMARY_KEYS_SQL)
|
||||
for (let table of primaryKeysResponse.rows) {
|
||||
const tableName = table.table_name
|
||||
if (!tableKeys[tableName]) {
|
||||
tableKeys[tableName] = []
|
||||
}
|
||||
tableKeys[tableName].push(table.column_name || table.primary_key)
|
||||
}
|
||||
} catch (err) {
|
||||
tableKeys = {}
|
||||
}
|
||||
|
||||
const columnsResponse = await this.client.query(this.COLUMNS_SQL)
|
||||
const tables: { [key: string]: Table } = {}
|
||||
|
||||
for (let column of columnsResponse.rows) {
|
||||
const tableName: string = column.table_name
|
||||
const columnName: string = column.column_name
|
||||
|
||||
// table key doesn't exist yet
|
||||
if (!tables[tableName] || !tables[tableName].schema) {
|
||||
tables[tableName] = {
|
||||
_id: buildExternalTableId(datasourceId, tableName),
|
||||
primary: tableKeys[tableName] || ["id"],
|
||||
name: tableName,
|
||||
schema: {},
|
||||
}
|
||||
}
|
||||
|
||||
const type: string = convertType(column.data_type, TYPE_MAP)
|
||||
tables[tableName].schema[columnName] = {
|
||||
name: columnName,
|
||||
type,
|
||||
}
|
||||
}
|
||||
this.tables = tables
|
||||
}
|
||||
|
||||
async create(sql: string) {
|
||||
const response = await internalQuery(this.client, { sql })
|
||||
return response.rows.length ? response.rows : [{created: true}]
|
||||
}
|
||||
|
||||
async read(sql: string) {
|
||||
const response = await internalQuery(this.client, { sql })
|
||||
return response.rows
|
||||
}
|
||||
|
||||
async update(sql: string) {
|
||||
const response = await internalQuery(this.client, { sql })
|
||||
return response.rows.length ? response.rows : [{updated: true}]
|
||||
}
|
||||
|
||||
async delete(sql: string) {
|
||||
const response = await internalQuery(this.client, { sql })
|
||||
return response.rows.length ? response.rows : [{deleted: true}]
|
||||
}
|
||||
|
||||
async query(json: QueryJson) {
|
||||
const operation = this._operation(json).toLowerCase()
|
||||
const input = this._query(json)
|
||||
const response = await internalQuery(this.client, input)
|
||||
return response.rows.length ? response.rows : [{[operation]: true}]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: PostgresIntegration,
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
const fetch = require("node-fetch")
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://github.com/node-fetch/node-fetch",
|
||||
description:
|
||||
"Representational state transfer (REST) is a de-facto standard for a software architecture for interactive applications that typically use multiple Web services. ",
|
||||
friendlyName: "REST API",
|
||||
datasource: {
|
||||
url: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
default: "localhost",
|
||||
required: true,
|
||||
},
|
||||
defaultHeaders: {
|
||||
type: FIELD_TYPES.OBJECT,
|
||||
required: false,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
readable: true,
|
||||
displayName: "POST",
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
urlDisplay: true,
|
||||
fields: {
|
||||
path: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
queryString: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
headers: {
|
||||
type: FIELD_TYPES.OBJECT,
|
||||
},
|
||||
requestBody: {
|
||||
type: FIELD_TYPES.JSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
displayName: "GET",
|
||||
readable: true,
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
urlDisplay: true,
|
||||
fields: {
|
||||
path: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
queryString: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
headers: {
|
||||
type: FIELD_TYPES.OBJECT,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
displayName: "PUT",
|
||||
readable: true,
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
urlDisplay: true,
|
||||
fields: {
|
||||
path: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
queryString: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
headers: {
|
||||
type: FIELD_TYPES.OBJECT,
|
||||
},
|
||||
requestBody: {
|
||||
type: FIELD_TYPES.JSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
displayName: "DELETE",
|
||||
type: QUERY_TYPES.FIELDS,
|
||||
urlDisplay: true,
|
||||
fields: {
|
||||
path: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
queryString: {
|
||||
type: FIELD_TYPES.STRING,
|
||||
},
|
||||
headers: {
|
||||
type: FIELD_TYPES.OBJECT,
|
||||
},
|
||||
requestBody: {
|
||||
type: FIELD_TYPES.JSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class RestIntegration {
|
||||
constructor(config) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
async parseResponse(response) {
|
||||
switch (this.headers.Accept) {
|
||||
case "application/json":
|
||||
return await response.json()
|
||||
case "text/html":
|
||||
return await response.text()
|
||||
default:
|
||||
return await response.json()
|
||||
}
|
||||
}
|
||||
|
||||
async create({ path = "", queryString = "", headers = {}, json }) {
|
||||
this.headers = {
|
||||
...this.config.defaultHeaders,
|
||||
...headers,
|
||||
}
|
||||
|
||||
const response = await fetch(this.config.url + path + queryString, {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(json),
|
||||
})
|
||||
|
||||
return await this.parseResponse(response)
|
||||
}
|
||||
|
||||
async read({ path = "", queryString = "", headers = {} }) {
|
||||
this.headers = {
|
||||
...this.config.defaultHeaders,
|
||||
...headers,
|
||||
}
|
||||
|
||||
const response = await fetch(this.config.url + path + queryString, {
|
||||
headers: this.headers,
|
||||
})
|
||||
|
||||
return await this.parseResponse(response)
|
||||
}
|
||||
|
||||
async update({ path = "", queryString = "", headers = {}, json }) {
|
||||
this.headers = {
|
||||
...this.config.defaultHeaders,
|
||||
...headers,
|
||||
}
|
||||
|
||||
const response = await fetch(this.config.url + path + queryString, {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(json),
|
||||
})
|
||||
|
||||
return await this.parseResponse(response)
|
||||
}
|
||||
|
||||
async delete({ path = "", queryString = "", headers = {} }) {
|
||||
this.headers = {
|
||||
...this.config.defaultHeaders,
|
||||
...headers,
|
||||
}
|
||||
|
||||
const response = await fetch(this.config.url + path + queryString, {
|
||||
method: "DELETE",
|
||||
headers: this.headers,
|
||||
})
|
||||
|
||||
return await this.parseResponse(response)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: RestIntegration,
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
import { Integration, DatasourceFieldTypes, QueryTypes } from "./base/definitions"
|
||||
|
||||
module RestModule {
|
||||
const fetch = require("node-fetch")
|
||||
|
||||
interface RestConfig {
|
||||
url: string,
|
||||
defaultHeaders: {
|
||||
[key: string]: any,
|
||||
}
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://github.com/node-fetch/node-fetch",
|
||||
description:
|
||||
"Representational state transfer (REST) is a de-facto standard for a software architecture for interactive applications that typically use multiple Web services. ",
|
||||
friendlyName: "REST API",
|
||||
datasource: {
|
||||
url: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "localhost",
|
||||
required: true,
|
||||
},
|
||||
defaultHeaders: {
|
||||
type: DatasourceFieldTypes.OBJECT,
|
||||
required: false,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
readable: true,
|
||||
displayName: "POST",
|
||||
type: QueryTypes.FIELDS,
|
||||
urlDisplay: true,
|
||||
fields: {
|
||||
path: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
queryString: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
headers: {
|
||||
type: DatasourceFieldTypes.OBJECT,
|
||||
},
|
||||
requestBody: {
|
||||
type: DatasourceFieldTypes.JSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
displayName: "GET",
|
||||
readable: true,
|
||||
type: QueryTypes.FIELDS,
|
||||
urlDisplay: true,
|
||||
fields: {
|
||||
path: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
queryString: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
headers: {
|
||||
type: DatasourceFieldTypes.OBJECT,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
displayName: "PUT",
|
||||
readable: true,
|
||||
type: QueryTypes.FIELDS,
|
||||
urlDisplay: true,
|
||||
fields: {
|
||||
path: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
queryString: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
headers: {
|
||||
type: DatasourceFieldTypes.OBJECT,
|
||||
},
|
||||
requestBody: {
|
||||
type: DatasourceFieldTypes.JSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
displayName: "DELETE",
|
||||
type: QueryTypes.FIELDS,
|
||||
urlDisplay: true,
|
||||
fields: {
|
||||
path: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
queryString: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
},
|
||||
headers: {
|
||||
type: DatasourceFieldTypes.OBJECT,
|
||||
},
|
||||
requestBody: {
|
||||
type: DatasourceFieldTypes.JSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class RestIntegration {
|
||||
private config: RestConfig
|
||||
private headers: {
|
||||
[key: string]: string
|
||||
} = {}
|
||||
|
||||
constructor(config: RestConfig) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
async parseResponse(response: any) {
|
||||
switch (this.headers.Accept) {
|
||||
case "application/json":
|
||||
return await response.json()
|
||||
case "text/html":
|
||||
return await response.text()
|
||||
default:
|
||||
return await response.json()
|
||||
}
|
||||
}
|
||||
|
||||
async create({ path = "", queryString = "", headers = {}, json = {} }) {
|
||||
this.headers = {
|
||||
...this.config.defaultHeaders,
|
||||
...headers,
|
||||
}
|
||||
|
||||
const response = await fetch(this.config.url + path + queryString, {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(json),
|
||||
})
|
||||
|
||||
return await this.parseResponse(response)
|
||||
}
|
||||
|
||||
async read({path = "", queryString = "", headers = {}}) {
|
||||
this.headers = {
|
||||
...this.config.defaultHeaders,
|
||||
...headers,
|
||||
}
|
||||
|
||||
const response = await fetch(this.config.url + path + queryString, {
|
||||
headers: this.headers,
|
||||
})
|
||||
|
||||
return await this.parseResponse(response)
|
||||
}
|
||||
|
||||
async update({path = "", queryString = "", headers = {}, json = {}}) {
|
||||
this.headers = {
|
||||
...this.config.defaultHeaders,
|
||||
...headers,
|
||||
}
|
||||
|
||||
const response = await fetch(this.config.url + path + queryString, {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(json),
|
||||
})
|
||||
|
||||
return await this.parseResponse(response)
|
||||
}
|
||||
|
||||
async delete({path = "", queryString = "", headers = {}}) {
|
||||
this.headers = {
|
||||
...this.config.defaultHeaders,
|
||||
...headers,
|
||||
}
|
||||
|
||||
const response = await fetch(this.config.url + path + queryString, {
|
||||
method: "DELETE",
|
||||
headers: this.headers,
|
||||
})
|
||||
|
||||
return await this.parseResponse(response)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: RestIntegration,
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
const AWS = require("aws-sdk")
|
||||
|
||||
const SCHEMA = {
|
||||
docs: "https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
|
||||
description:
|
||||
"Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance.",
|
||||
friendlyName: "Amazon S3",
|
||||
datasource: {
|
||||
region: {
|
||||
type: "string",
|
||||
required: true,
|
||||
default: "us-east-1",
|
||||
},
|
||||
accessKeyId: {
|
||||
type: "password",
|
||||
required: true,
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: "password",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
read: {
|
||||
type: "fields",
|
||||
fields: {
|
||||
bucket: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class S3Integration {
|
||||
constructor(config) {
|
||||
this.config = config
|
||||
this.connect()
|
||||
this.client = new AWS.S3()
|
||||
}
|
||||
|
||||
async connect() {
|
||||
AWS.config.update(this.config)
|
||||
}
|
||||
|
||||
async read(query) {
|
||||
const response = await this.client
|
||||
.listObjects({
|
||||
Bucket: query.bucket,
|
||||
})
|
||||
.promise()
|
||||
return response.Contents
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: S3Integration,
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import { Integration, QueryTypes } from "./base/definitions"
|
||||
|
||||
module S3Module {
|
||||
const AWS = require("aws-sdk")
|
||||
|
||||
interface S3Config {
|
||||
region: string,
|
||||
accessKeyId: string,
|
||||
secretAccessKey: string,
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
|
||||
description:
|
||||
"Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance.",
|
||||
friendlyName: "Amazon S3",
|
||||
datasource: {
|
||||
region: {
|
||||
type: "string",
|
||||
required: true,
|
||||
default: "us-east-1",
|
||||
},
|
||||
accessKeyId: {
|
||||
type: "password",
|
||||
required: true,
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: "password",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
read: {
|
||||
type: QueryTypes.FIELDS,
|
||||
fields: {
|
||||
bucket: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class S3Integration {
|
||||
private readonly config: S3Config
|
||||
private client: any
|
||||
private connectionPromise: Promise<any>
|
||||
|
||||
constructor(config: S3Config) {
|
||||
this.config = config
|
||||
this.connectionPromise = this.connect()
|
||||
this.client = new AWS.S3()
|
||||
}
|
||||
|
||||
async connect() {
|
||||
AWS.config.update(this.config)
|
||||
}
|
||||
|
||||
async read(query: { bucket: string }) {
|
||||
const response = await this.client
|
||||
.listObjects({
|
||||
Bucket: query.bucket,
|
||||
})
|
||||
.promise()
|
||||
return response.Contents
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: S3Integration,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"lib": ["es6"],
|
||||
"allowJs": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/*.json",
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.js"
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue