Updates to use our new Nano layer for CouchDB integration rather than PouchDB.

This commit is contained in:
mike12345567 2023-01-31 19:49:31 +00:00
parent 9f3cb5934b
commit f4379fcb4f
5 changed files with 124 additions and 1038 deletions

View File

@ -15,18 +15,36 @@ import { getCouchInfo } from "./connections"
import { directCouchCall } from "./utils"
import { getPouchDB } from "./pouchDB"
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 {
public readonly name: string
private static nano: Nano.ServerScope
private readonly instanceNano?: Nano.ServerScope
private readonly pouchOpts: DatabaseOpts
constructor(dbName?: string, opts?: DatabaseOpts) {
constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
if (dbName == null) {
throw new Error("Database name cannot be undefined.")
}
this.name = dbName
this.pouchOpts = opts || {}
if (connection) {
const couchInfo = getCouchInfo(connection)
this.instanceNano = buildNano(couchInfo)
}
if (!DatabaseImpl.nano) {
DatabaseImpl.init()
}
@ -34,15 +52,7 @@ export class DatabaseImpl implements Database {
static init() {
const couchInfo = getCouchInfo()
DatabaseImpl.nano = Nano({
url: couchInfo.url,
requestDefaults: {
headers: {
Authorization: couchInfo.cookie,
},
},
parseUrl: false,
})
DatabaseImpl.nano = buildNano(couchInfo)
}
async exists() {
@ -50,6 +60,10 @@ export class DatabaseImpl implements Database {
return response.status === 200
}
private nano() {
return this.instanceNano || DatabaseImpl.nano
}
async checkSetup() {
let shouldCreate = !this.pouchOpts?.skip_setup
// check exists in a lightweight fashion
@ -58,9 +72,9 @@ export class DatabaseImpl implements Database {
throw new Error("DB does not exist")
}
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) {
@ -101,6 +115,13 @@ export class DatabaseImpl implements Database {
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) {
if (!document._id) {
throw new Error("Cannot store document without _id field.")
@ -146,7 +167,7 @@ export class DatabaseImpl implements Database {
async destroy() {
try {
await DatabaseImpl.nano.db.destroy(this.name)
await this.nano().db.destroy(this.name)
} catch (err: any) {
// didn't exist, don't worry
if (err.statusCode === 404) {

View File

@ -1,7 +1,7 @@
import env from "../../environment"
export const getCouchInfo = () => {
const urlInfo = getUrlInfo()
export const getCouchInfo = (connection?: string) => {
const urlInfo = getUrlInfo(connection)
let username
let password
if (env.COUCH_DB_USERNAME) {

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
import {
Integration,
DatasourceFieldType,
QueryType,
Document,
Integration,
IntegrationBase,
QueryType,
} from "@budibase/types"
const PouchDB = require("pouchdb")
import { db as dbCore } from "@budibase/backend-core"
interface CouchDBConfig {
url: string
@ -39,6 +39,15 @@ const SCHEMA: Integration = {
update: {
type: QueryType.JSON,
},
get: {
type: QueryType.FIELDS,
fields: {
id: {
type: DatasourceFieldType.STRING,
required: true,
},
},
},
delete: {
type: QueryType.FIELDS,
fields: {
@ -57,7 +66,11 @@ class CouchDBIntegration implements IntegrationBase {
constructor(config: CouchDBConfig) {
this.config = config
this.client = new PouchDB(`${config.url}/${config.database}`)
this.client = new dbCore.DatabaseImpl(
config.database,
undefined,
config.url
)
}
async query(
@ -66,31 +79,48 @@ class CouchDBIntegration implements IntegrationBase {
query: { json?: object; id?: string }
) {
try {
const response = await this.client[command](query.id || query.json)
await this.client.close()
return response
return await this.client[command](query.id || query.json)
} catch (err) {
console.error(errorMsg, err)
throw err
}
}
async create(query: { json: object }) {
return this.query("post", "Error writing to couchDB", query)
private parse(query: { json: string | object }) {
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", {
json: {
include_docs: true,
...query.json,
...parsed,
},
})
return result.rows.map((row: { doc: object }) => row.doc)
}
async update(query: { json: object }) {
return this.query("put", "Error updating couchDB document", query)
async update(query: { json: string | object }) {
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 }) {

View File

@ -1,16 +1,19 @@
jest.mock(
"pouchdb",
() =>
function CouchDBMock(this: any) {
this.post = jest.fn()
this.allDocs = jest.fn(() => ({ rows: [] }))
this.put = jest.fn()
this.get = jest.fn()
this.remove = jest.fn()
this.plugin = jest.fn()
this.close = jest.fn()
}
)
jest.mock("@budibase/backend-core", () => {
const core = jest.requireActual("@budibase/backend-core")
return {
...core,
db: {
...core.db,
DatabaseImpl: function () {
this.post = jest.fn()
this.allDocs = jest.fn().mockReturnValue({ rows: [] })
this.put = jest.fn()
this.get = jest.fn().mockReturnValue({ _rev: "a" })
this.remove = jest.fn()
},
},
}
})
import { default as CouchDBIntegration } from "../couchdb"
@ -33,8 +36,8 @@ describe("CouchDB Integration", () => {
const doc = {
test: 1,
}
const response = await config.integration.create({
json: doc,
await config.integration.create({
json: JSON.stringify(doc),
})
expect(config.integration.client.post).toHaveBeenCalledWith(doc)
})
@ -44,8 +47,8 @@ describe("CouchDB Integration", () => {
name: "search",
}
const response = await config.integration.read({
json: doc,
await config.integration.read({
json: JSON.stringify(doc),
})
expect(config.integration.client.allDocs).toHaveBeenCalledWith({
@ -60,11 +63,14 @@ describe("CouchDB Integration", () => {
name: "search",
}
const response = await config.integration.update({
json: doc,
await config.integration.update({
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 () => {