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,26 +15,10 @@ import { getCouchInfo } from "./connections"
|
|||
import { directCouchCall } from "./utils"
|
||||
import { getPouchDB } from "./pouchDB"
|
||||
import { WriteStream, ReadStream } from "fs"
|
||||
import { newid } from "../../newid"
|
||||
|
||||
export class DatabaseImpl implements Database {
|
||||
public readonly name: string
|
||||
private static nano: Nano.ServerScope
|
||||
private readonly pouchOpts: DatabaseOpts
|
||||
|
||||
constructor(dbName?: string, opts?: DatabaseOpts) {
|
||||
if (dbName == null) {
|
||||
throw new Error("Database name cannot be undefined.")
|
||||
}
|
||||
this.name = dbName
|
||||
this.pouchOpts = opts || {}
|
||||
if (!DatabaseImpl.nano) {
|
||||
DatabaseImpl.init()
|
||||
}
|
||||
}
|
||||
|
||||
static init() {
|
||||
const couchInfo = getCouchInfo()
|
||||
DatabaseImpl.nano = Nano({
|
||||
function buildNano(couchInfo: { url: string; cookie: string }) {
|
||||
return Nano({
|
||||
url: couchInfo.url,
|
||||
requestDefaults: {
|
||||
headers: {
|
||||
|
@ -43,6 +27,32 @@ export class DatabaseImpl implements Database {
|
|||
},
|
||||
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, 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()
|
||||
}
|
||||
}
|
||||
|
||||
static init() {
|
||||
const couchInfo = getCouchInfo()
|
||||
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) {
|
||||
|
|
|
@ -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
|
@ -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 }) {
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
jest.mock(
|
||||
"pouchdb",
|
||||
() =>
|
||||
function CouchDBMock(this: any) {
|
||||
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(() => ({ rows: [] }))
|
||||
this.allDocs = jest.fn().mockReturnValue({ rows: [] })
|
||||
this.put = jest.fn()
|
||||
this.get = jest.fn()
|
||||
this.get = jest.fn().mockReturnValue({ _rev: "a" })
|
||||
this.remove = jest.fn()
|
||||
this.plugin = jest.fn()
|
||||
this.close = 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 () => {
|
||||
|
|
Loading…
Reference in a new issue