Updates to use our new Nano layer for CouchDB integration rather than PouchDB.
This commit is contained in:
parent
9f3cb5934b
commit
f4379fcb4f
5 changed files with 124 additions and 1038 deletions
|
@ -15,18 +15,36 @@ import { getCouchInfo } from "./connections"
|
||||||
import { directCouchCall } from "./utils"
|
import { directCouchCall } from "./utils"
|
||||||
import { getPouchDB } from "./pouchDB"
|
import { getPouchDB } from "./pouchDB"
|
||||||
import { WriteStream, ReadStream } from "fs"
|
import { WriteStream, ReadStream } from "fs"
|
||||||
|
import { newid } from "../../newid"
|
||||||
|
|
||||||
|
function buildNano(couchInfo: { url: string; cookie: string }) {
|
||||||
|
return Nano({
|
||||||
|
url: couchInfo.url,
|
||||||
|
requestDefaults: {
|
||||||
|
headers: {
|
||||||
|
Authorization: couchInfo.cookie,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parseUrl: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export class DatabaseImpl implements Database {
|
export class DatabaseImpl implements Database {
|
||||||
public readonly name: string
|
public readonly name: string
|
||||||
private static nano: Nano.ServerScope
|
private static nano: Nano.ServerScope
|
||||||
|
private readonly instanceNano?: Nano.ServerScope
|
||||||
private readonly pouchOpts: DatabaseOpts
|
private readonly pouchOpts: DatabaseOpts
|
||||||
|
|
||||||
constructor(dbName?: string, opts?: DatabaseOpts) {
|
constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
|
||||||
if (dbName == null) {
|
if (dbName == null) {
|
||||||
throw new Error("Database name cannot be undefined.")
|
throw new Error("Database name cannot be undefined.")
|
||||||
}
|
}
|
||||||
this.name = dbName
|
this.name = dbName
|
||||||
this.pouchOpts = opts || {}
|
this.pouchOpts = opts || {}
|
||||||
|
if (connection) {
|
||||||
|
const couchInfo = getCouchInfo(connection)
|
||||||
|
this.instanceNano = buildNano(couchInfo)
|
||||||
|
}
|
||||||
if (!DatabaseImpl.nano) {
|
if (!DatabaseImpl.nano) {
|
||||||
DatabaseImpl.init()
|
DatabaseImpl.init()
|
||||||
}
|
}
|
||||||
|
@ -34,15 +52,7 @@ export class DatabaseImpl implements Database {
|
||||||
|
|
||||||
static init() {
|
static init() {
|
||||||
const couchInfo = getCouchInfo()
|
const couchInfo = getCouchInfo()
|
||||||
DatabaseImpl.nano = Nano({
|
DatabaseImpl.nano = buildNano(couchInfo)
|
||||||
url: couchInfo.url,
|
|
||||||
requestDefaults: {
|
|
||||||
headers: {
|
|
||||||
Authorization: couchInfo.cookie,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
parseUrl: false,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async exists() {
|
async exists() {
|
||||||
|
@ -50,6 +60,10 @@ export class DatabaseImpl implements Database {
|
||||||
return response.status === 200
|
return response.status === 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private nano() {
|
||||||
|
return this.instanceNano || DatabaseImpl.nano
|
||||||
|
}
|
||||||
|
|
||||||
async checkSetup() {
|
async checkSetup() {
|
||||||
let shouldCreate = !this.pouchOpts?.skip_setup
|
let shouldCreate = !this.pouchOpts?.skip_setup
|
||||||
// check exists in a lightweight fashion
|
// check exists in a lightweight fashion
|
||||||
|
@ -58,9 +72,9 @@ export class DatabaseImpl implements Database {
|
||||||
throw new Error("DB does not exist")
|
throw new Error("DB does not exist")
|
||||||
}
|
}
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
await DatabaseImpl.nano.db.create(this.name)
|
await this.nano().db.create(this.name)
|
||||||
}
|
}
|
||||||
return DatabaseImpl.nano.db.use(this.name)
|
return this.nano().db.use(this.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateOutput(fnc: any) {
|
private async updateOutput(fnc: any) {
|
||||||
|
@ -101,6 +115,13 @@ export class DatabaseImpl implements Database {
|
||||||
return this.updateOutput(() => db.destroy(_id, _rev))
|
return this.updateOutput(() => db.destroy(_id, _rev))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async post(document: AnyDocument, opts?: DatabasePutOpts) {
|
||||||
|
if (!document._id) {
|
||||||
|
document._id = newid()
|
||||||
|
}
|
||||||
|
return this.put(document, opts)
|
||||||
|
}
|
||||||
|
|
||||||
async put(document: AnyDocument, opts?: DatabasePutOpts) {
|
async put(document: AnyDocument, opts?: DatabasePutOpts) {
|
||||||
if (!document._id) {
|
if (!document._id) {
|
||||||
throw new Error("Cannot store document without _id field.")
|
throw new Error("Cannot store document without _id field.")
|
||||||
|
@ -146,7 +167,7 @@ export class DatabaseImpl implements Database {
|
||||||
|
|
||||||
async destroy() {
|
async destroy() {
|
||||||
try {
|
try {
|
||||||
await DatabaseImpl.nano.db.destroy(this.name)
|
await this.nano().db.destroy(this.name)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// didn't exist, don't worry
|
// didn't exist, don't worry
|
||||||
if (err.statusCode === 404) {
|
if (err.statusCode === 404) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
|
|
||||||
export const getCouchInfo = () => {
|
export const getCouchInfo = (connection?: string) => {
|
||||||
const urlInfo = getUrlInfo()
|
const urlInfo = getUrlInfo(connection)
|
||||||
let username
|
let username
|
||||||
let password
|
let password
|
||||||
if (env.COUCH_DB_USERNAME) {
|
if (env.COUCH_DB_USERNAME) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,11 +1,11 @@
|
||||||
import {
|
import {
|
||||||
Integration,
|
|
||||||
DatasourceFieldType,
|
DatasourceFieldType,
|
||||||
QueryType,
|
Document,
|
||||||
|
Integration,
|
||||||
IntegrationBase,
|
IntegrationBase,
|
||||||
|
QueryType,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { db as dbCore } from "@budibase/backend-core"
|
||||||
const PouchDB = require("pouchdb")
|
|
||||||
|
|
||||||
interface CouchDBConfig {
|
interface CouchDBConfig {
|
||||||
url: string
|
url: string
|
||||||
|
@ -39,6 +39,15 @@ const SCHEMA: Integration = {
|
||||||
update: {
|
update: {
|
||||||
type: QueryType.JSON,
|
type: QueryType.JSON,
|
||||||
},
|
},
|
||||||
|
get: {
|
||||||
|
type: QueryType.FIELDS,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: DatasourceFieldType.STRING,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
delete: {
|
delete: {
|
||||||
type: QueryType.FIELDS,
|
type: QueryType.FIELDS,
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -57,7 +66,11 @@ class CouchDBIntegration implements IntegrationBase {
|
||||||
|
|
||||||
constructor(config: CouchDBConfig) {
|
constructor(config: CouchDBConfig) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.client = new PouchDB(`${config.url}/${config.database}`)
|
this.client = new dbCore.DatabaseImpl(
|
||||||
|
config.database,
|
||||||
|
undefined,
|
||||||
|
config.url
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(
|
async query(
|
||||||
|
@ -66,31 +79,48 @@ class CouchDBIntegration implements IntegrationBase {
|
||||||
query: { json?: object; id?: string }
|
query: { json?: object; id?: string }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const response = await this.client[command](query.id || query.json)
|
return await this.client[command](query.id || query.json)
|
||||||
await this.client.close()
|
|
||||||
return response
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(errorMsg, err)
|
console.error(errorMsg, err)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(query: { json: object }) {
|
private parse(query: { json: string | object }) {
|
||||||
return this.query("post", "Error writing to couchDB", query)
|
return typeof query.json === "string" ? JSON.parse(query.json) : query.json
|
||||||
}
|
}
|
||||||
|
|
||||||
async read(query: { json: object }) {
|
async create(query: { json: string | object }) {
|
||||||
|
const parsed = this.parse(query)
|
||||||
|
return this.query("post", "Error writing to couchDB", { json: parsed })
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(query: { json: string | object }) {
|
||||||
|
const parsed = this.parse(query)
|
||||||
const result = await this.query("allDocs", "Error querying couchDB", {
|
const result = await this.query("allDocs", "Error querying couchDB", {
|
||||||
json: {
|
json: {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
...query.json,
|
...parsed,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return result.rows.map((row: { doc: object }) => row.doc)
|
return result.rows.map((row: { doc: object }) => row.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(query: { json: object }) {
|
async update(query: { json: string | object }) {
|
||||||
return this.query("put", "Error updating couchDB document", query)
|
const parsed: Document = this.parse(query)
|
||||||
|
if (!parsed?._rev && parsed?._id) {
|
||||||
|
const oldDoc = await this.get({ id: parsed._id })
|
||||||
|
parsed._rev = oldDoc._rev
|
||||||
|
}
|
||||||
|
return this.query("put", "Error updating couchDB document", {
|
||||||
|
json: parsed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(query: { id: string }) {
|
||||||
|
return this.query("get", "Error retrieving couchDB document by ID", {
|
||||||
|
id: query.id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(query: { id: string }) {
|
async delete(query: { id: string }) {
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
jest.mock(
|
jest.mock("@budibase/backend-core", () => {
|
||||||
"pouchdb",
|
const core = jest.requireActual("@budibase/backend-core")
|
||||||
() =>
|
return {
|
||||||
function CouchDBMock(this: any) {
|
...core,
|
||||||
this.post = jest.fn()
|
db: {
|
||||||
this.allDocs = jest.fn(() => ({ rows: [] }))
|
...core.db,
|
||||||
this.put = jest.fn()
|
DatabaseImpl: function () {
|
||||||
this.get = jest.fn()
|
this.post = jest.fn()
|
||||||
this.remove = jest.fn()
|
this.allDocs = jest.fn().mockReturnValue({ rows: [] })
|
||||||
this.plugin = jest.fn()
|
this.put = jest.fn()
|
||||||
this.close = jest.fn()
|
this.get = jest.fn().mockReturnValue({ _rev: "a" })
|
||||||
}
|
this.remove = jest.fn()
|
||||||
)
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
import { default as CouchDBIntegration } from "../couchdb"
|
import { default as CouchDBIntegration } from "../couchdb"
|
||||||
|
|
||||||
|
@ -33,8 +36,8 @@ describe("CouchDB Integration", () => {
|
||||||
const doc = {
|
const doc = {
|
||||||
test: 1,
|
test: 1,
|
||||||
}
|
}
|
||||||
const response = await config.integration.create({
|
await config.integration.create({
|
||||||
json: doc,
|
json: JSON.stringify(doc),
|
||||||
})
|
})
|
||||||
expect(config.integration.client.post).toHaveBeenCalledWith(doc)
|
expect(config.integration.client.post).toHaveBeenCalledWith(doc)
|
||||||
})
|
})
|
||||||
|
@ -44,8 +47,8 @@ describe("CouchDB Integration", () => {
|
||||||
name: "search",
|
name: "search",
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await config.integration.read({
|
await config.integration.read({
|
||||||
json: doc,
|
json: JSON.stringify(doc),
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(config.integration.client.allDocs).toHaveBeenCalledWith({
|
expect(config.integration.client.allDocs).toHaveBeenCalledWith({
|
||||||
|
@ -60,11 +63,14 @@ describe("CouchDB Integration", () => {
|
||||||
name: "search",
|
name: "search",
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await config.integration.update({
|
await config.integration.update({
|
||||||
json: doc,
|
json: JSON.stringify(doc),
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(config.integration.client.put).toHaveBeenCalledWith(doc)
|
expect(config.integration.client.put).toHaveBeenCalledWith({
|
||||||
|
...doc,
|
||||||
|
_rev: "a",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls the delete method with the correct params", async () => {
|
it("calls the delete method with the correct params", async () => {
|
||||||
|
|
Loading…
Reference in a new issue