ueberdb2 2.1.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ on:
6
6
  branches:
7
7
  - main
8
8
  - master
9
+ - v[0-9]+
9
10
 
10
11
  jobs:
11
12
  test:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Notable Changes
2
2
 
3
+ ## v2.2.0
4
+
5
+ Compatibility changes:
6
+
7
+ * Passing callbacks to the database methods is deprecated; use the returned
8
+ Promises instead.
9
+
10
+ New features:
11
+
12
+ * Database methods now return a Promise if a callback is not provided.
13
+
14
+ Bug fixes:
15
+
16
+ * A call to `flush()` immediately after a call to `set()`, `setSub()`, or
17
+ `remove()` (within the same ECMAScript macro- or microtask) now flushes the
18
+ new write operation.
19
+ * Fixed a bug where `findKeys()` would return stale results when write
20
+ buffering is enabled and writes are pending.
21
+ * `couch`: Rewrote driver to fix numerous bugs.
22
+
3
23
  ## v2.1.1
4
24
 
5
25
  Security fix:
package/README.md CHANGED
@@ -40,58 +40,48 @@ npm install ueberdb2
40
40
  ```javascript
41
41
  const ueberdb = require('ueberdb2');
42
42
 
43
- // mysql
44
- const db = new ueberdb.Database('mysql', {
45
- user: 'root',
46
- host: 'localhost',
47
- password: '',
48
- database: 'store',
49
- engine: 'InnoDB',
50
- });
51
-
52
- // dirty to file system
53
- //const db = new ueberdb.Database('dirty', {filename: 'var/dirty.db'});
54
-
55
- async function example(db) {
56
- await db.init();
57
-
58
- // no need for await because it's already in cache.
59
- db.set('valueA', {a: 1, b: 2});
60
-
61
- db.get('valueA', function (err, value) {
62
- // close the database connection.
63
- db.close(function () {
64
- process.exit(0);
65
- });
43
+ (async () => {
44
+ // mysql
45
+ const db = new ueberdb.Database('mysql', {
46
+ user: 'root',
47
+ host: 'localhost',
48
+ password: '',
49
+ database: 'store',
50
+ engine: 'InnoDB',
66
51
  });
67
- }
52
+ // dirty to file system
53
+ //const db = new ueberdb.Database('dirty', {filename: 'var/dirty.db'});
68
54
 
69
- example(db);
55
+ await db.init();
56
+ try {
57
+ await db.set('valueA', {a: 1, b: 2});
58
+ console.log('valueA is', await db.get('valueA'));
59
+ } finally {
60
+ await db.close();
61
+ }
62
+ })();
70
63
  ```
71
64
 
72
65
  ### findKeys
73
66
 
74
67
  ```javascript
75
68
  const ueberdb = require('ueberdb2');
76
- const db = new ueberdb.Database('dirty', {filename: 'var/dirty.db'});
77
69
 
78
- async function example(db){
70
+ (async () => {
71
+ const db = new ueberdb.Database('dirty', {filename: 'var/dirty.db'});
79
72
  await db.init();
80
-
81
- // no need for await because it's already in cache.
82
- db.set('valueA', {a: 1, b: 2});
83
- db.set('valueA:h1', {a: 1, b: 2});
84
- db.set('valueA:h2', {a: 3, b: 4});
85
-
86
- db.findKeys('valueA:*', null, function (err, value) { // TODO: Check this
87
- // value will be ['valueA:h1', 'valueA:h2']
88
- db.close(function () {
89
- process.exit(0);
90
- });
91
- });
92
- }
93
-
94
- example(db);
73
+ try {
74
+ await Promise.all([
75
+ db.set('valueA', {a: 1, b: 2}),
76
+ db.set('valueA:h1', {a: 1, b: 2}),
77
+ db.set('valueA:h2', {a: 3, b: 4}),
78
+ ]);
79
+ // prints [ 'valueA:h1', 'valueA:h2' ]
80
+ console.log(await db.findKeys('valueA:*', null));
81
+ } finally {
82
+ await db.close();
83
+ }
84
+ })();
95
85
  ```
96
86
 
97
87
  ### Getting and setting subkeys
@@ -103,6 +93,7 @@ methods make this easier.
103
93
  #### `getSub`
104
94
 
105
95
  ```javascript
96
+ const value = await db.getSub(key, propertyPath);
106
97
  db.getSub(key, propertyPath, callback);
107
98
  ```
108
99
 
@@ -115,40 +106,31 @@ exist or if the given property path does not exist.
115
106
  Examples:
116
107
 
117
108
  ```javascript
118
- db.set(key, {prop1: {prop2: ['value']}}, (err) => {
119
- if (err != null) throw err;
109
+ (async () => {
110
+ await db.set(key, {prop1: {prop2: ['value']}});
120
111
 
121
- db.getSub(key, ['prop1', 'prop2', '0'], (err, val) => {
122
- if (err != null) throw err;
123
- console.log('1.', val); // prints "1. value"
124
- });
112
+ const val1 = await db.getSub(key, ['prop1', 'prop2', '0']);
113
+ console.log('1.', val1); // prints "1. value"
125
114
 
126
- db.getSub(key, ['prop1', 'prop2'], (err, val) => {
127
- if (err != null) throw err;
128
- console.log('2.', val); // prints "2. [ 'value' ]"
129
- });
115
+ const val2 = await db.getSub(key, ['prop1', 'prop2']);
116
+ console.log('2.', val2); // prints "2. [ 'value' ]"
130
117
 
131
- db.getSub(key, ['prop1'], (err, val) => {
132
- if (err != null) throw err;
133
- console.log('3.', val); // prints "3. { prop2: [ 'value' ] }"
134
- });
118
+ const val3 = await db.getSub(key, ['prop1']);
119
+ console.log('3.', val3); // prints "3. { prop2: [ 'value' ] }"
135
120
 
136
- db.getSub(key, [], (err, val) => {
137
- if (err != null) throw err;
138
- console.log('4.', val); // prints "4. { prop1: { prop2: [ 'value' ] } }"
139
- });
121
+ const val4 = await db.getSub(key, []);
122
+ console.log('4.', val4); // prints "4. { prop1: { prop2: [ 'value' ] } }"
140
123
 
141
- db.getSub(key, ['does', 'not', 'exist'], (err, val) => {
142
- if (err != null) throw err;
143
- console.log('5.', val); // prints "5. null" or "5. undefined"
144
- });
124
+ const val5 = await db.getSub(key, ['does', 'not', 'exist']);
125
+ console.log('5.', val5); // prints "5. null" or "5. undefined"
145
126
  });
146
127
  ```
147
128
 
148
129
  #### `setSub`
149
130
 
150
131
  ```javascript
151
- db.setSub(key, propertyPath, value, cb);
132
+ await db.setSub(key, propertyPath, value);
133
+ db.setSub(key, propertyPath, value, callback);
152
134
  ```
153
135
 
154
136
  Fetches the object stored at `key`, walks the property path given in
@@ -156,30 +138,26 @@ Fetches the object stored at `key`, walks the property path given in
156
138
  must be an array. If `propertyPath` is an empty array then `setSub()` is
157
139
  equivalent to `set()`. Empty objects are created as needed if the property path
158
140
  does not exist (including if `key` does not exist in the database). It is an
159
- error to attempt to set a property on a non-object. `cb` is optional and is
160
- called when the database driver has reported that the change has been written.
141
+ error to attempt to set a property on a non-object.
161
142
 
162
143
  Examples:
163
144
 
164
145
  ```javascript
165
- // Assumption: The database does not yet have any records.
146
+ // Assumption: db does not yet have any records.
147
+ (async () => {
148
+ // Equivalent to db.set('key1', 'value'):
149
+ await db.setSub('key1', [], 'value');
166
150
 
167
- // Equivalent to db.set('key1', 'value', cb):
168
- db.setSub('key1', [], 'value', cb);
151
+ // Equivalent to db.set('key2', {prop1: {prop2: {0: 'value'}}}):
152
+ await db.setSub('key2', ['prop1', 'prop2', '0'], 'value'):
169
153
 
170
- // Equivalent to db.set('key2', {prop1: {prop2: {0: 'value'}}}, cb):
171
- db.setSub('key2', ['prop1', 'prop2', '0'], 'value', cb):
154
+ await db.set('key3', {prop1: 'value'});
172
155
 
173
- db.set('key3', {prop1: 'value'}, (err) => {
174
- if (err != null) return cb(err);
175
- // Equivalent to db.set('key3', {prop1: 'value', prop2: 'other value'}, cb):
176
- db.setSub('key3', ['prop2'], 'other value', cb);
177
- });
156
+ // Equivalent to db.set('key3', {prop1: 'value', prop2: 'other value'}):
157
+ await db.setSub('key3', ['prop2'], 'other value');
178
158
 
179
- db.set('key3', {prop1: 'value'}, (err) => {
180
- if (err != null) return cb(err);
181
159
  // TypeError: Cannot set property "badProp" on non-object "value":
182
- db.setSub('key3', ['prop1', 'badProp'], 'foo', cb);
160
+ await db.setSub('key3', ['prop1', 'badProp'], 'foo');
183
161
  });
184
162
  ```
185
163
 
@@ -196,13 +174,13 @@ const ueberdb = require('ueberdb2');
196
174
  const db = new ueberdb.Database(
197
175
  'dirty', {filename: 'var/dirty.db'}, {cache: 0});
198
176
  await db.init();
199
- db.set('valueA', {a: 1, b: 2});
200
- db.get('valueA', (err, value) => {
177
+ try {
178
+ await db.set('valueA', {a: 1, b: 2});
179
+ const value = await db.get('valueA');
201
180
  console.log(JSON.stringify(value));
202
- db.close(() => {
203
- process.exit(0);
204
- });
205
- });
181
+ } finally {
182
+ await db.close();
183
+ }
206
184
  })();
207
185
  ```
208
186
 
@@ -218,13 +196,13 @@ const ueberdb = require('ueberdb2');
218
196
  const db = new ueberdb.Database(
219
197
  'dirty', {filename: 'var/dirty.db'}, {writeInterval: 0});
220
198
  await db.init();
221
- db.set('valueA', {a: 1, b: 2});
222
- db.get('valueA', (err, value) => {
199
+ try {
200
+ await db.set('valueA', {a: 1, b: 2});
201
+ const value = await db.get('valueA');
223
202
  console.log(JSON.stringify(value));
224
- db.close(() => {
225
- process.exit(0);
226
- });
227
- });
203
+ } finally {
204
+ await db.close();
205
+ }
228
206
  })();
229
207
  ```
230
208
 
@@ -16,21 +16,14 @@
16
16
  */
17
17
 
18
18
  const AbstractDatabase = require('../lib/AbstractDatabase');
19
+ const http = require('http');
19
20
  const nano = require('nano');
20
- const async = require('async');
21
-
22
- const DESIGN_NAME = 'ueberDb';
23
- const DESIGN_PATH = `_design/${DESIGN_NAME}`;
24
-
25
- const handleError = (er) => {
26
- if (er) throw new Error(er);
27
- };
28
21
 
29
22
  exports.Database = class extends AbstractDatabase {
30
23
  constructor(settings) {
31
24
  super();
25
+ this.agent = null;
32
26
  this.db = null;
33
- this.client = null;
34
27
  this.settings = settings;
35
28
 
36
29
  // force some settings
@@ -40,162 +33,110 @@ exports.Database = class extends AbstractDatabase {
40
33
  this.settings.json = false;
41
34
  }
42
35
 
43
- init(callback) {
44
- const settings = this.settings;
45
- let client = null;
46
- let db = null;
36
+ get isAsync() { return true; }
47
37
 
48
- const config = {
49
- url: `http://${settings.host}:${settings.port}`,
38
+ async init() {
39
+ this.agent = new http.Agent({
40
+ keepAlive: true,
41
+ maxSockets: this.settings.maxListeners || 1,
42
+ });
43
+ const client = nano({
44
+ url: `http://${this.settings.host}:${this.settings.port}`,
50
45
  requestDefaults: {
51
- pool: {
52
- maxSockets: settings.maxListeners || 1,
53
- },
54
46
  auth: {
55
- user: settings.user,
56
- pass: settings.password,
47
+ username: this.settings.user,
48
+ password: this.settings.password,
57
49
  },
50
+ httpAgent: this.agent,
58
51
  },
59
- };
60
-
61
- const createDb = () => {
62
- client.db.create(settings.database, (er, body) => {
63
- if (er) return callback(er);
64
- return setDb();
65
- });
66
- };
67
-
68
- const setDb = () => {
69
- db = client.use(settings.database);
70
- checkUeberDbDesignDocument(db);
71
- this.client = client;
72
- this.db = db;
73
- callback();
74
- };
75
-
76
- // Always ensure that couchDb has at least an empty design doc for UeberDb use
77
- // this will be necessary for the `findKeys` method
78
- const checkUeberDbDesignDocument = () => {
79
- db.head(DESIGN_PATH, (er, _, header) => {
80
- if (er && er.statusCode === 404) return db.insert({views: {}}, DESIGN_PATH, handleError);
81
- if (er) throw new Error(er);
82
- });
83
- };
84
-
85
- client = nano(config);
86
- client.db.get(settings.database, (er, body) => {
87
- if (er && er.statusCode === 404) return createDb();
88
- if (er) return callback(er);
89
- return setDb();
90
52
  });
53
+ try {
54
+ await client.db.get(this.settings.database);
55
+ } catch (err) {
56
+ if (err.statusCode !== 404) throw err;
57
+ await client.db.create(this.settings.database);
58
+ }
59
+ this.db = client.use(this.settings.database);
91
60
  }
92
61
 
93
- get(key, callback) {
94
- const db = this.db;
95
- db.get(key, (er, doc) => {
96
- if (er && er.statusCode !== 404) {
97
- console.log('GET');
98
- console.log(er);
99
- }
100
- if (doc == null) return callback(null, null);
101
- callback(null, doc.value);
102
- });
62
+ async get(key) {
63
+ let doc;
64
+ try {
65
+ doc = await this.db.get(key);
66
+ } catch (err) {
67
+ if (err.statusCode === 404) return null;
68
+ throw err;
69
+ }
70
+ return doc.value;
103
71
  }
104
72
 
105
- findKeys(key, notKey, callback) {
106
- const regex = this.createFindRegex(key, notKey);
107
- const queryKey = `${key}__${notKey}`;
108
- const db = this.db;
109
-
110
- // always look up if the query haven't be done before
111
- const checkQuery = () => {
112
- db.get(DESIGN_PATH, (er, doc) => {
113
- handleError(er);
114
- const queryExists = queryKey in doc.views;
115
- if (!queryExists) return createQuery(doc);
116
- makeQuery();
117
- });
118
- };
119
-
120
- // Cache the query for faster reuse in the future
121
- const createQuery = (doc) => {
122
- const mapFunction = {
123
- map: `function (doc) { if (${regex}.test(doc._id)) { emit(doc._id, null); } }`,
124
- };
125
- doc.views[queryKey] = mapFunction;
126
- db.insert(doc, DESIGN_PATH, (er) => {
127
- handleError(er);
128
- makeQuery();
129
- });
130
- };
131
-
132
- // If this is the first time the request is used, this can take a while…
133
- const makeQuery = (er) => {
134
- db.view(DESIGN_NAME, queryKey, (er, docs) => {
135
- handleError(er);
136
- docs = docs.rows.map((doc) => doc.key);
137
- callback(null, docs);
138
- });
139
- };
140
-
141
- checkQuery();
73
+ async findKeys(key, notKey) {
74
+ const pfxLen = key.indexOf('*');
75
+ const pfx = pfxLen < 0 ? key : key.slice(0, pfxLen);
76
+ const results = await this.db.find({
77
+ selector: {
78
+ _id: pfxLen < 0 ? pfx : {
79
+ $gte: pfx,
80
+ // https://docs.couchdb.org/en/3.2.2/ddocs/views/collation.html#string-ranges
81
+ $lte: `${pfx}\ufff0`,
82
+ $regex: this.createFindRegex(key, notKey).source,
83
+ },
84
+ },
85
+ fields: ['_id'],
86
+ });
87
+ return results.docs.map((doc) => doc._id);
142
88
  }
143
89
 
144
- set(key, value, callback) {
145
- const db = this.db;
146
- db.get(key, (er, doc) => {
147
- if (doc == null) return db.insert({_id: key, value}, callback);
148
- db.insert({_id: key, _rev: doc._rev, value}, callback);
90
+ async set(key, value) {
91
+ let doc;
92
+ try {
93
+ doc = await this.db.get(key);
94
+ } catch (err) {
95
+ if (err.statusCode !== 404) throw err;
96
+ }
97
+ await this.db.insert({
98
+ _id: key,
99
+ value,
100
+ ...doc == null ? {} : {
101
+ _rev: doc._rev,
102
+ },
149
103
  });
150
104
  }
151
105
 
152
- remove(key, callback) {
153
- const db = this.db;
154
- db.head(key, (er, _, header) => {
155
- if (er && er.statusCode === 404) return callback(null);
156
- if (er) return callback(er);
157
- // etag has additional quotation marks, remove them
158
- const etag = JSON.parse(header).etag;
159
- db.destroy(key, etag, (er, body) => {
160
- if (er) return callback(er);
161
- callback(null);
162
- });
163
- });
106
+ async remove(key) {
107
+ let header;
108
+ try {
109
+ header = await this.db.head(key);
110
+ } catch (err) {
111
+ if (err.statusCode === 404) return;
112
+ throw err;
113
+ }
114
+ // etag has additional quotation marks, remove them
115
+ const etag = JSON.parse(header.etag);
116
+ await this.db.destroy(key, etag);
164
117
  }
165
118
 
166
- doBulk(bulk, callback) {
167
- const db = this.db;
119
+ async doBulk(bulk) {
168
120
  const keys = bulk.map((op) => op.key);
169
121
  const revs = {};
122
+ for (const {key, value} of (await this.db.fetchRevs({keys})).rows) {
123
+ // couchDB will return error instead of value if key does not exist
124
+ if (value != null) revs[key] = value.rev;
125
+ }
170
126
  const setters = [];
171
- async.series([
172
- (callback) => {
173
- db.fetchRevs({keys}, (er, r) => {
174
- if (er) throw new Error(JSON.stringify(er));
175
- const rows = r.rows;
176
- for (const j in r.rows) {
177
- // couchDB will return error instead of value if key does not exist
178
- if (rows[j].value != null) revs[rows[j].key] = rows[j].value.rev;
179
- }
180
- callback();
181
- });
182
- },
183
- (callback) => {
184
- for (const item of bulk) {
185
- const set = {_id: item.key};
186
- if (revs[item.key] != null) set._rev = revs[item.key];
187
- if (item.type === 'set') set.value = item.value;
188
- if (item.type === 'remove') set._deleted = true;
189
- setters.push(set);
190
- }
191
- callback();
192
- },
193
- ], (err) => {
194
- db.bulk({docs: setters}, callback);
195
- });
127
+ for (const item of bulk) {
128
+ const set = {_id: item.key};
129
+ if (revs[item.key] != null) set._rev = revs[item.key];
130
+ if (item.type === 'set') set.value = item.value;
131
+ if (item.type === 'remove') set._deleted = true;
132
+ setters.push(set);
133
+ }
134
+ await this.db.bulk({docs: setters});
196
135
  }
197
136
 
198
- close(callback) {
199
- if (callback) callback();
137
+ async close() {
138
+ this.db = null;
139
+ if (this.agent) this.agent.destroy();
140
+ this.agent = null;
200
141
  }
201
142
  };