247 lines
7.6 KiB
TypeScript
247 lines
7.6 KiB
TypeScript
var PouchDB = require("pouchdb-node");
|
|
PouchDB.plugin(require("pouchdb-find"));
|
|
var path = require("path");
|
|
var { app } = require("electron");
|
|
var fs = require("fs");
|
|
|
|
const dbs: Record<string, any> = {};
|
|
|
|
/**
|
|
* Create a collection on data store
|
|
*
|
|
* @param name name of the collection to create
|
|
* @param schema schema of the collection to create, include fields and their types
|
|
* @returns Promise<void>
|
|
*
|
|
*/
|
|
function createCollection(name: string, schema?: { [key: string]: any }): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
const dbPath = path.join(appPath(), "databases");
|
|
if (!fs.existsSync(dbPath)) fs.mkdirSync(dbPath);
|
|
const db = new PouchDB(`${path.join(dbPath, name)}`);
|
|
dbs[name] = db;
|
|
resolve();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete a collection
|
|
*
|
|
* @param name name of the collection to delete
|
|
* @returns Promise<void>
|
|
*
|
|
*/
|
|
function deleteCollection(name: string): Promise<void> {
|
|
// Do nothing with Unstructured Database
|
|
return dbs[name].destroy();
|
|
}
|
|
|
|
/**
|
|
* Insert a value to a collection
|
|
*
|
|
* @param collectionName name of the collection
|
|
* @param value value to insert
|
|
* @returns Promise<any>
|
|
*
|
|
*/
|
|
function insertOne(collectionName: string, value: any): Promise<any> {
|
|
if (!value._id) return dbs[collectionName].post(value).then((doc) => doc.id);
|
|
return dbs[collectionName].put(value).then((doc) => doc.id);
|
|
}
|
|
|
|
/**
|
|
* Update value of a collection's record
|
|
*
|
|
* @param collectionName name of the collection
|
|
* @param key key of the record to update
|
|
* @param value value to update
|
|
* @returns Promise<void>
|
|
*
|
|
*/
|
|
function updateOne(collectionName: string, key: string, value: any): Promise<void> {
|
|
console.debug(`updateOne ${collectionName}: ${key} - ${JSON.stringify(value)}`);
|
|
return dbs[collectionName].get(key).then((doc) => {
|
|
return dbs[collectionName].put({
|
|
_id: key,
|
|
_rev: doc._rev,
|
|
...value,
|
|
},
|
|
{ force: true });
|
|
}).then((res: any) => {
|
|
console.info(`updateOne ${collectionName} result: ${JSON.stringify(res)}`);
|
|
}).catch((err: any) => {
|
|
console.error(`updateOne ${collectionName} error: ${err}`);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update value of a collection's records
|
|
*
|
|
* @param collectionName name of the collection
|
|
* @param selector selector of records to update
|
|
* @param value value to update
|
|
* @returns Promise<void>
|
|
*
|
|
*/
|
|
function updateMany(collectionName: string, value: any, selector?: { [key: string]: any }): Promise<any> {
|
|
// Creates keys from selector for indexing
|
|
const keys = selector ? Object.keys(selector) : [];
|
|
|
|
// At a basic level, there are two steps to running a query: createIndex()
|
|
// (to define which fields to index) and find() (to query the index).
|
|
return (
|
|
keys.length > 0
|
|
? dbs[collectionName].createIndex({
|
|
// There is selector so we need to create index
|
|
index: { fields: keys },
|
|
})
|
|
: Promise.resolve()
|
|
) // No selector, so no need to create index
|
|
.then(() =>
|
|
dbs[collectionName].find({
|
|
// Find documents using Mango queries
|
|
selector,
|
|
})
|
|
)
|
|
.then((data) => {
|
|
const docs = data.docs.map((doc) => {
|
|
// Update doc with new value
|
|
return (doc = {
|
|
...doc,
|
|
...value,
|
|
});
|
|
});
|
|
return dbs[collectionName].bulkDocs(docs);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete a collection's record
|
|
*
|
|
* @param collectionName name of the collection
|
|
* @param key key of the record to delete
|
|
* @returns Promise<void>
|
|
*
|
|
*/
|
|
function deleteOne(collectionName: string, key: string): Promise<void> {
|
|
return findOne(collectionName, key).then((doc) => dbs[collectionName].remove(doc));
|
|
}
|
|
|
|
/**
|
|
* Delete a collection records by selector
|
|
*
|
|
* @param {string} collectionName name of the collection
|
|
* @param {{ [key: string]: any }} selector selector for retrieving records.
|
|
* @returns Promise<void>
|
|
*
|
|
*/
|
|
function deleteMany(collectionName: string, selector?: { [key: string]: any }): Promise<void> {
|
|
// Creates keys from selector for indexing
|
|
const keys = selector ? Object.keys(selector) : [];
|
|
|
|
// At a basic level, there are two steps to running a query: createIndex()
|
|
// (to define which fields to index) and find() (to query the index).
|
|
return (
|
|
keys.length > 0
|
|
? dbs[collectionName].createIndex({
|
|
// There is selector so we need to create index
|
|
index: { fields: keys },
|
|
})
|
|
: Promise.resolve()
|
|
) // No selector, so no need to create index
|
|
.then(() =>
|
|
dbs[collectionName].find({
|
|
// Find documents using Mango queries
|
|
selector,
|
|
})
|
|
)
|
|
.then((data) => {
|
|
return Promise.all(
|
|
// Remove documents
|
|
data.docs.map((doc) => {
|
|
return dbs[collectionName].remove(doc);
|
|
})
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Retrieve a record from a collection in the data store.
|
|
* @param {string} collectionName - The name of the collection containing the record to retrieve.
|
|
* @param {string} key - The key of the record to retrieve.
|
|
* @returns {Promise<any>} A promise that resolves when the record is retrieved.
|
|
*/
|
|
function findOne(collectionName: string, key: string): Promise<any> {
|
|
return dbs[collectionName].get(key).catch(() => undefined);
|
|
}
|
|
|
|
/**
|
|
* Gets records in a collection in the data store using a selector.
|
|
* @param {string} collectionName - The name of the collection containing records to retrieve.
|
|
* @param {{ [key: string]: any }} selector - The selector to use to retrieve records.
|
|
* @param {[{ [key: string]: any }]} sort - The sort options to use to retrieve records.
|
|
* @returns {Promise<any>} A promise that resolves with the selected records.
|
|
*/
|
|
function findMany(
|
|
collectionName: string,
|
|
selector?: { [key: string]: any },
|
|
sort?: [{ [key: string]: any }]
|
|
): Promise<any> {
|
|
const keys = selector ? Object.keys(selector) : [];
|
|
const sortKeys = sort ? sort.flatMap((e) => (e ? Object.keys(e) : undefined)) : [];
|
|
|
|
// Note that we are specifying that the field must be greater than or equal to null
|
|
// which is a workaround for the fact that the Mango query language requires us to have a selector.
|
|
// In CouchDB collation order, null is the "lowest" value, and so this will return all documents regardless of their field value.
|
|
sortKeys.forEach((key) => {
|
|
if (!keys.includes(key)) {
|
|
selector = { ...selector, [key]: { $gt: null } };
|
|
}
|
|
});
|
|
|
|
// There is no selector & sort, so we can just use allDocs() to get all the documents.
|
|
if (sortKeys.concat(keys).length === 0) {
|
|
return dbs[collectionName]
|
|
.allDocs({
|
|
include_docs: true,
|
|
endkey: "_design",
|
|
inclusive_end: false,
|
|
})
|
|
.then((data) => data.rows.map((row) => row.doc));
|
|
}
|
|
// At a basic level, there are two steps to running a query: createIndex()
|
|
// (to define which fields to index) and find() (to query the index).
|
|
return dbs[collectionName]
|
|
.createIndex({
|
|
// Create index for selector & sort
|
|
index: { fields: sortKeys.concat(keys) },
|
|
})
|
|
.then(() => {
|
|
// Find documents using Mango queries
|
|
return dbs[collectionName].find({
|
|
selector,
|
|
sort,
|
|
});
|
|
})
|
|
.then((data) => data.docs); // Return documents
|
|
}
|
|
|
|
function appPath() {
|
|
if (app) {
|
|
return app.getPath("userData");
|
|
}
|
|
return process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share");
|
|
}
|
|
|
|
module.exports = {
|
|
createCollection,
|
|
deleteCollection,
|
|
insertOne,
|
|
findOne,
|
|
findMany,
|
|
updateOne,
|
|
updateMany,
|
|
deleteOne,
|
|
deleteMany,
|
|
};
|