CouchDBを構築する
This commit is contained in:
parent
fa31073482
commit
c91e2006b4
17
examles/sampleCouchDB.js
Normal file
17
examles/sampleCouchDB.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
async function main() {
|
||||||
|
const {CouchDatabaseClient} = require('../src/classes/couchdb/CouchDatabaseClient');
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
protocol: 'http',
|
||||||
|
host: 'localhost',
|
||||||
|
port: 5984,
|
||||||
|
username: 'admin',
|
||||||
|
password: 'secret'
|
||||||
|
}
|
||||||
|
const client = new CouchDatabaseClient(config);
|
||||||
|
const users = client.getCollection('user');
|
||||||
|
|
||||||
|
const user = await users.create({name: 'testUser1', email: 'testUser1@example.com' });
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
174
package-lock.json
generated
174
package-lock.json
generated
@ -13,7 +13,8 @@
|
|||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"csv-writer": "^1.6.0",
|
"csv-writer": "^1.6.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"minio": "^8.0.5"
|
"minio": "^8.0.5",
|
||||||
|
"nano": "^10.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jsdoc": "^4.0.4"
|
"jsdoc": "^4.0.4"
|
||||||
@ -373,6 +374,32 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||||
|
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/axios/node_modules/form-data": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/b4a": {
|
"node_modules/b4a": {
|
||||||
"version": "1.6.7",
|
"version": "1.6.7",
|
||||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
|
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
|
||||||
@ -927,6 +954,26 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||||
@ -1711,6 +1758,26 @@
|
|||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nano": {
|
||||||
|
"version": "10.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/nano/-/nano-10.1.4.tgz",
|
||||||
|
"integrity": "sha512-bJOFIPLExIbF6mljnfExXX9Cub4W0puhDjVMp+qV40xl/DBvgKao7St4+6/GB6EoHZap7eFnrnx4mnp5KYgwJA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.4",
|
||||||
|
"node-abort-controller": "^3.1.1",
|
||||||
|
"qs": "^6.13.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-abort-controller": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/node-fetch": {
|
"node_modules/node-fetch": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
@ -1740,6 +1807,18 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-inspect": {
|
||||||
|
"version": "1.13.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||||
|
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
@ -1819,6 +1898,12 @@
|
|||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/punycode.js": {
|
"node_modules/punycode.js": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||||
@ -1829,6 +1914,21 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||||
|
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/query-string": {
|
"node_modules/query-string": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
|
||||||
@ -1998,6 +2098,78 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/side-channel": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"object-inspect": "^1.13.3",
|
||||||
|
"side-channel-list": "^1.0.0",
|
||||||
|
"side-channel-map": "^1.0.1",
|
||||||
|
"side-channel-weakmap": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-list": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"object-inspect": "^1.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-map": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-weakmap": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3",
|
||||||
|
"side-channel-map": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/signal-exit": {
|
"node_modules/signal-exit": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"csv-writer": "^1.6.0",
|
"csv-writer": "^1.6.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"minio": "^8.0.5"
|
"minio": "^8.0.5",
|
||||||
|
"nano": "^10.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jsdoc": "^4.0.4"
|
"jsdoc": "^4.0.4"
|
||||||
|
97
src/classes/couchdb/CouchCollection.js
Normal file
97
src/classes/couchdb/CouchCollection.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// couchdb/CouchCollection.js
|
||||||
|
const { CouchDocument } = require('./CouchDocument.js');
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
class CouchCollection {
|
||||||
|
/**
|
||||||
|
* @param {import('nano').DocumentScope<any>} db
|
||||||
|
*/
|
||||||
|
constructor(db) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {Promise<import('../interfaces').IDocument<T> | null>}
|
||||||
|
*/
|
||||||
|
async get(id) {
|
||||||
|
try {
|
||||||
|
const doc = await this.db.get(id);
|
||||||
|
return new CouchDocument(
|
||||||
|
doc._id,
|
||||||
|
doc.data,
|
||||||
|
doc.createdAt ? new Date(doc.createdAt) : undefined,
|
||||||
|
doc.updatedAt ? new Date(doc.updatedAt) : undefined,
|
||||||
|
doc._rev
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.statusCode === 404) return null;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<Array<import('../interfaces').IDocument<T>>>}
|
||||||
|
*/
|
||||||
|
async list() {
|
||||||
|
const result = await this.db.list({ include_docs: true });
|
||||||
|
return result.rows
|
||||||
|
.filter(row => row.doc)
|
||||||
|
.map(row =>
|
||||||
|
new CouchDocument(
|
||||||
|
row.doc._id,
|
||||||
|
row.doc.data,
|
||||||
|
row.doc.createdAt ? new Date(row.doc.createdAt) : undefined,
|
||||||
|
row.doc.updatedAt ? new Date(row.doc.updatedAt) : undefined,
|
||||||
|
row.doc._rev
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {T} data
|
||||||
|
* @returns {Promise<import('../interfaces').IDocument<T>>}
|
||||||
|
*/
|
||||||
|
async create(data) {
|
||||||
|
const now = new Date();
|
||||||
|
const doc = {
|
||||||
|
data,
|
||||||
|
createdAt: now.toISOString(),
|
||||||
|
updatedAt: now.toISOString(),
|
||||||
|
};
|
||||||
|
const response = await this.db.insert(doc);
|
||||||
|
return new CouchDocument(response.id, data, now, now, response.rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @param {Partial<T>} data
|
||||||
|
* @returns {Promise<import('../interfaces').IDocument<T>>}
|
||||||
|
*/
|
||||||
|
async update(id, data) {
|
||||||
|
const existing = await this.db.get(id);
|
||||||
|
const updated = {
|
||||||
|
...existing,
|
||||||
|
data: {
|
||||||
|
...existing.data,
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
const response = await this.db.insert(updated);
|
||||||
|
return new CouchDocument(response.id, updated.data, existing.createdAt, new Date(), response.rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async delete(id) {
|
||||||
|
const doc = await this.db.get(id);
|
||||||
|
await this.db.destroy(id, doc._rev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { CouchCollection };
|
40
src/classes/couchdb/CouchDatabaseClient.js
Normal file
40
src/classes/couchdb/CouchDatabaseClient.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// couchdb/CouchDatabaseClient.js
|
||||||
|
const nano = require('nano');
|
||||||
|
const { CouchCollection } = require('./CouchCollection.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {import('../interfaces').ICollection<T>} ICollection
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CouchDBConfig
|
||||||
|
* @property {string} protocol
|
||||||
|
* @property {string} host
|
||||||
|
* @property {number} port
|
||||||
|
* @property {string} username
|
||||||
|
* @property {string} password
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CouchDatabaseClient {
|
||||||
|
/**
|
||||||
|
* @param {CouchDBConfig} config
|
||||||
|
*/
|
||||||
|
constructor(config) {
|
||||||
|
const { protocol, host, port, username, password } = config;
|
||||||
|
const url = `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}`;
|
||||||
|
this.server = nano(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {ICollection<T>}
|
||||||
|
*/
|
||||||
|
getCollection(name) {
|
||||||
|
const db = this.server.db.use(name);
|
||||||
|
return new CouchCollection(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { CouchDatabaseClient };
|
34
src/classes/couchdb/CouchDocument.js
Normal file
34
src/classes/couchdb/CouchDocument.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// couchdb/CouchDocument.js
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {Object} IDocument
|
||||||
|
* @property {string} id
|
||||||
|
* @property {T} data
|
||||||
|
* @property {Date=} createdAt
|
||||||
|
* @property {Date=} updatedAt
|
||||||
|
* @property {string|number=} version
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @implements {IDocument<T>}
|
||||||
|
*/
|
||||||
|
class CouchDocument {
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @param {T} data
|
||||||
|
* @param {Date=} createdAt
|
||||||
|
* @param {Date=} updatedAt
|
||||||
|
* @param {string|number=} version
|
||||||
|
*/
|
||||||
|
constructor(id, data, createdAt, updatedAt, version) {
|
||||||
|
this.id = id;
|
||||||
|
this.data = data;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { CouchDocument };
|
175
src/script/couchDB/README.md
Normal file
175
src/script/couchDB/README.md
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
# [NoSQL][CouchDB]Node.jsの利用ガイド(nanoライブラリ)
|
||||||
|
|
||||||
|
CouchDBはドキュメント指向のNoSQLデータベースで、
|
||||||
|
HTTPベースのRESTful APIで操作可能です。
|
||||||
|
|
||||||
|
Node.jsでは公式の軽量クライアントであるnanoを使うことで
|
||||||
|
簡単にCouchDBとやりとりできます。
|
||||||
|
|
||||||
|
[戻る](https://wiki.pglikers.com/e/en/private/db/nosql/couch-db)
|
||||||
|
|
||||||
|
|
||||||
|
## nanoのインストール
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install nano
|
||||||
|
```
|
||||||
|
|
||||||
|
nanoはNode.js向けに設計されているため
|
||||||
|
ブラウザ(フロントエンド)では使用できません。
|
||||||
|
|
||||||
|
## CouchDBとの接続
|
||||||
|
|
||||||
|
```sh
|
||||||
|
const nano = require('nano')('http://admin:secret@localhost:5984');
|
||||||
|
```
|
||||||
|
|
||||||
|
**envなどを活用して設定する場合**
|
||||||
|
|
||||||
|
```conf
|
||||||
|
COUCHDB_HOST=localhost
|
||||||
|
COUCHDB_PORT=5984
|
||||||
|
COUCHDB_USER=admin
|
||||||
|
COUCHDB_PASSWORD=secret
|
||||||
|
```
|
||||||
|
|
||||||
|
サンプルコード
|
||||||
|
|
||||||
|
```js
|
||||||
|
require('dotenv').config();
|
||||||
|
const protocol = 'http'; // or https
|
||||||
|
const {
|
||||||
|
COUCHDB_HOST,
|
||||||
|
COUCHDB_PORT,
|
||||||
|
COUCHDB_USER,
|
||||||
|
COUCHDB_PASSWORD
|
||||||
|
} = process.env;
|
||||||
|
const url = `${protocol}://${COUCHDB_USER}:${COUCHDB_PASSWORD}@${COUCHDB_HOST}:${COUCHDB_PORT}`;
|
||||||
|
const nano = require('nano')(url);
|
||||||
|
```
|
||||||
|
|
||||||
|
## CouchDBの使い方
|
||||||
|
|
||||||
|
### データベースの操作
|
||||||
|
|
||||||
|
```js
|
||||||
|
// データベース作成
|
||||||
|
await nano.db.create('user');
|
||||||
|
|
||||||
|
// データベース削除
|
||||||
|
await nano.db.destroy('user');
|
||||||
|
|
||||||
|
// 一覧取得
|
||||||
|
const dbs = await nano.db.list();
|
||||||
|
console.log(dbs);
|
||||||
|
```
|
||||||
|
|
||||||
|
### ドキュメントの操作
|
||||||
|
|
||||||
|
```js
|
||||||
|
const db = nano.db.use('user');
|
||||||
|
|
||||||
|
// 作成
|
||||||
|
await db.insert({ name: 'Alice', email: 'alice@example.com' });
|
||||||
|
|
||||||
|
// 取得
|
||||||
|
const doc = await db.get('document_id');
|
||||||
|
|
||||||
|
// 更新
|
||||||
|
doc.age = 30;
|
||||||
|
await db.insert(doc);
|
||||||
|
|
||||||
|
// 削除
|
||||||
|
await db.destroy(doc._id, doc._rev);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### インデックス作成(パフォーマンス向上)
|
||||||
|
|
||||||
|
Mangoクエリ(find)はインデックスがあると高速になります。
|
||||||
|
例としてユーザーの年齢でよく検索するなら、
|
||||||
|
事前に下記のようにインデックスを作ることを推奨します
|
||||||
|
|
||||||
|
```js
|
||||||
|
await db.createIndex({
|
||||||
|
index: { fields: ['age'] },
|
||||||
|
name: 'age-index',
|
||||||
|
type: 'json'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
### nanoの初期化オプション
|
||||||
|
|
||||||
|
```js
|
||||||
|
const nano = require('nano')({
|
||||||
|
url: 'http://localhost:5984',
|
||||||
|
requestDefaults: {
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Basic ...' // 任意のカスタムヘッダー
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 検索のサンプル
|
||||||
|
|
||||||
|
#### 名前が'A'から始まる人だけ取得
|
||||||
|
|
||||||
|
CouchDBのMangoクエリでは文字列の部分一致は
|
||||||
|
範囲指定($gte, $lt)を使う方法が一般的です
|
||||||
|
("startkey" などでは存在しない)
|
||||||
|
|
||||||
|
```js
|
||||||
|
const result = await db.find({
|
||||||
|
selector: {
|
||||||
|
name: {
|
||||||
|
"$gte": "A",
|
||||||
|
"$lt": "B"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sort: [{ name: "asc" }]
|
||||||
|
});
|
||||||
|
result.docs.forEach(doc => console.log(doc));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OR条件を使いたい($or)
|
||||||
|
|
||||||
|
```js
|
||||||
|
const result = await db.find({
|
||||||
|
selector: {
|
||||||
|
"$or": [
|
||||||
|
{ age: { "$lt": 30 } },
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
"$gte": "Z",
|
||||||
|
"$lt": "Z\ufff0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
複数条件やソートを行う場合は
|
||||||
|
インデックスも複数フィールドに対応しておくと効率が良くなります
|
||||||
|
|
||||||
|
```js
|
||||||
|
await db.createIndex({
|
||||||
|
index: {
|
||||||
|
fields: ['age', 'name']
|
||||||
|
},
|
||||||
|
name: 'age-name-index'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### フロントエンドで使えるのか
|
||||||
|
|
||||||
|
認証情報がURLに含まれるためセキュリティ上もフロントでの使用は不適切
|
||||||
|
|
||||||
|
* フロントエンドからCouchDBを扱いたい場合:
|
||||||
|
1. **PouchDBを活用する**
|
||||||
|
* フロント向けCouchDB互換DB。リアルタイム同期・オフライン対応も可能
|
||||||
|
2. サーバー側でnanoを使いREST APIを作ってフロントから叩く
|
||||||
|
|
48
src/script/couchDB/controlDB.js
Normal file
48
src/script/couchDB/controlDB.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Create a new database
|
||||||
|
* nanoでデータベースを作成する
|
||||||
|
*/
|
||||||
|
const nano = require('nano')('http://admin:secret@localhost:5984');
|
||||||
|
|
||||||
|
// データベース作成
|
||||||
|
const createDB = async (dbName) => {
|
||||||
|
nano.db.create(dbName, (err, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// データベース削除
|
||||||
|
const deleteDB = async (dbName) => {
|
||||||
|
nano.db.destroy(dbName, (err, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// データベース一覧表示
|
||||||
|
const getDbList = async () => {
|
||||||
|
nano.db.list((err, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// await deleteDB('user');
|
||||||
|
// await createDB('product');
|
||||||
|
// await createDB('item');
|
||||||
|
// await getDbList();
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
85
src/script/couchDB/controlDoc.js
Normal file
85
src/script/couchDB/controlDoc.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Create a new database
|
||||||
|
* nanoでデータベースを作成する
|
||||||
|
*/
|
||||||
|
const nano = require('nano')('http://admin:secret@localhost:5984');
|
||||||
|
|
||||||
|
// ドキュメント作成
|
||||||
|
const createDocument = async (dbName,post) => {
|
||||||
|
const db = nano.use(dbName);
|
||||||
|
db.insert(post, (err, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ドキュメントを一覧を取得する
|
||||||
|
const getDocumentList = async (dbName) => {
|
||||||
|
const db = nano.use(dbName);
|
||||||
|
const result = await db.list({ include_docs: true });
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ドキュメントを一覧を取得する(抽出する)
|
||||||
|
const findDocument = async (dbName,selector) => {
|
||||||
|
const db = nano.use(dbName);
|
||||||
|
const result = await db.find({selector: selector});
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ドキュメントを取得する
|
||||||
|
const getDocument = async (dbName,docId) => {
|
||||||
|
const db = nano.use(dbName);
|
||||||
|
const doc = await db.get(docId);
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ドキュメントを更新する
|
||||||
|
const updateDocument = async (dbName,doc) => {
|
||||||
|
const db = nano.use(dbName);
|
||||||
|
return await db.insert(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ドキュメントを削除する
|
||||||
|
const deleteDocument = async (dbName,doc) => {
|
||||||
|
const db = nano.use(dbName);
|
||||||
|
return await db.destroy(doc._id, doc._rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// ドキュメントを作成する
|
||||||
|
await createDocument('user',{id:1,name:'Alice',email:'alice@example.com'});
|
||||||
|
// IDを指定する
|
||||||
|
await createDocument('user',{_id:'user_1',id:2,name:'Bob',email:'bob@example.com'});
|
||||||
|
|
||||||
|
// ドキュメントを取得する
|
||||||
|
const doc = await getDocument('user','user_1');
|
||||||
|
console.log(doc.name);
|
||||||
|
|
||||||
|
// ドキュメントを更新する
|
||||||
|
doc.age = 20;
|
||||||
|
await updateDocument('user',doc);
|
||||||
|
|
||||||
|
// ドキュメントを削除する
|
||||||
|
await deleteDocument('user',doc);
|
||||||
|
|
||||||
|
// ドキュメントの一覧を取得する
|
||||||
|
const docs = await getDocumentList('user');
|
||||||
|
console.log(docs);
|
||||||
|
|
||||||
|
// ドキュメントの検索する
|
||||||
|
const selector = {name: 'Bob'};
|
||||||
|
docs = await findDocument('user',selector);
|
||||||
|
console.log(docs);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
37
src/types/INoSQL.ts
Normal file
37
src/types/INoSQL.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Interface for NoSQL document
|
||||||
|
* @param T - Type of data
|
||||||
|
* @param id - Document ID
|
||||||
|
*/
|
||||||
|
export interface IDocument<T> {
|
||||||
|
id: string;
|
||||||
|
data: T;
|
||||||
|
createdAt?: Date;
|
||||||
|
updatedAt?: Date;
|
||||||
|
version?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to unsubscribe from updates *
|
||||||
|
* watch()を使わない・購読解除の必要もないならvoid戻り値でも問題なし
|
||||||
|
* 将来的には、unsubscribeするための関数を返す
|
||||||
|
*/
|
||||||
|
export type UnsubscribeFn = () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for NoSQL collection
|
||||||
|
* @param T - Type of data
|
||||||
|
*/
|
||||||
|
export interface ICollection<T> {
|
||||||
|
get(id: string): Promise<IDocument<T> | null>;
|
||||||
|
list(): Promise<IDocument<T>[]>;
|
||||||
|
create(data: T): Promise<IDocument<T>>;
|
||||||
|
update(id: string, data: Partial<T>): Promise<IDocument<T>>;
|
||||||
|
delete(id: string): Promise<void>;
|
||||||
|
// オプション: 更新監視など
|
||||||
|
watch?(onChange: (doc: IDocument<T>) => void): UnsubscribeFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDatabaseClient {
|
||||||
|
getCollection<T>(name: string): ICollection<T>;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user