couchdb-designer
With this package you can easily manage your couchdb design documents by storing them in directory structure and create javascript object from them. Chouchdb-designer provide two functions for that purpose: The first "designer" wait for a path of root directory of multiple design documents and gives back the array of design document objects. The second "createDesignDocument" do the same but only with one design document. Another feature is the "createTestContext" which allows you to testing your design document with jest testing framework.
Warnings
The design document generation doesn't check if the directory structure matching to the rules of couchdb design document syntax, although able to generate any type of them without attachmented. For proper use you need to know this rules. By testing you can discover many case of different missable usage.
generating design documents
It is work the way. if a directory then becomes to object type field and a file becomes to string or object field depend on rules belove:
- If the file is json file then becomes field contain the json file content.
- If the file not json or js file then becomes to String field.
- If the file is in shape name.lib.js then becomes to String field named as the filename name part and containing the js file content and becomes to available as a common js library in testing case.
- If the file is a js file contain a function or functions for example view map function and reduce function then additional rules apply.
- These functions must be named. (This is a benefit because the syntax check doesn't indicated as wrong.)
- The functions must be exported with module.exports = { functionName, otherFunction }
- If the file contain only one function with the same name as file itself then becomes to String field containing the proper function implementation. Otherwise if it contain more then one function or different named function then becomes to object type field with the proper content.
By the feature: js file contain only one function with the same name as file itself then becomes to String field. You can create more sophisticated structure. For example if you have several update functions writen in a single updates.js file you can even create an updates directory with additional files followed rules of same name function. This way the result will be an updates object containing the updates.js and the updates directory content.
Example directory structure for two design documents:
design
├── appdesign
│ ├── lib
│ │ └── couchdb.lib.js
│ ├── options.json
│ ├── updates
│ │ └── updateFomDir.js
│ ├── updates.js
│ ├── validate_doc_update.js
│ └── views
│ ├── byDate
│ │ ├── map.js
│ │ └── reduce.js
│ ├── byName
│ │ └── map.js
│ └── byParent.js
└── querys
├── language.txt
└── views
├── bar-index.json
└── foo-index
├── map.json
├── options.json
└── reduce.txt
Create multiple design documents from root directory of them.
import {designer,createDesignDocument} from '@zargu/couchdb-designer';
designer('./design').then(documents => {
},err => console.log(err));
Create single design document.
createDesignDocument('./design/appdesign').then(document => {
},err => console.log(err));
Testing
With createTestContext you can create a context represented by directory by the same way like at createDesignDocument but you can here declare a testDatabase in the second parameter. This context object has the same structure as design ducument has but with invokeable functions. These functions in the context object have the near same environment as in a real couchdb. Some of these functions by them nature return result which you can use testing with jest easily. But what if you want to test something like a view's map function which doesn't return the result directly, only call the couchdb built-in emit and maybe log functions. In these cases you can call the context as a function with the "emitted" or "logged" string parameter for get the indirect result of previously called functions. After calling the previously gathered data will be deleted but among two calling of them gathering every indirect data. The rest built-in couchdb functions is mockFunctions and available in the same way by calling the context as a function and give their name as a string parameter, for example context("registerType") will give you the given built-in mockFunction. When calling the available functions under the context object, they will verify their own implementation then throws error if something wrong happen, for example when calling irrelevant built in function.
Map/reduce testing.
An other but much better way of view testing instead of emitted is the calling the context with server parameter which give back an object what you can use as simulator of couchdb. For example context("server").view.viewname() insted of context.views.viewname.map(). For this opportunity you have to set the testDatabase with the createTestContext second parameter.The testDatabase is an array of objects. With server object you can testing the given view in context of map/reduce,grouping and the previously setted testDatabase. The server object's functions result the same as if you get by the given function's result from a real couchdb. Only the view functions supported yet and these waiting for an optional object parameter with reduce (boolean), group (boolean), group_level (integer) field with same meaning like the couchdb's viewFunction query parameters. These functions return the correct result even if you set one of built-in couchdb reducers instead of self implemented.
import { createTestContext } from '@zargu/couchdb-designer';
const testDatabase = [
{_id:'doc1'...},
{_id:'doc2'...}
...
]
describe('couchdb',() => {
beforeEach(() => {
jest.clearAllMocks();
});
test('appdesign',() => {
return createTestContext('design/adddesign',testDatabase).then(context => {
let somedocument = {_id:'some',mail:'foo@bar.com'};
expect(context.views.byMail.map(somedocument)).toBeUndefined();
expect(context.views.byMail.map.mock.calls.length).toBe(1)
expect(context.views.lib.someLibfunction.mock.calls.length).toBe(1);
expect(context('emitted').rows).toEqual([{id:'some',key:'foo@bar.com',value:1}]);
expect(context('logged')).toMatchSnapshot();
expect(context('registerType')).not.toHaveBeenCalled();
expect(context('server').views.byPeriod({group_level:1})).toEqual({rows:[{key:[2021],value:234}]})
}).catch(err => expect(err).toBe('something wrong in directory structure'));
});
});
Release note:
I hope this form will be the last. I always try to make an uniform resolv.
I hope i don't causing too much torment with my english.