ueberdb2 2.1.1 → 2.2.3
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.
- package/.github/workflows/npmpublish.yml +1 -0
- package/CHANGELOG.md +20 -0
- package/README.md +70 -92
- package/databases/couch_db.js +85 -144
- package/index.js +64 -84
- package/lib/AbstractDatabase.js +5 -0
- package/lib/CacheAndBufferLayer.js +95 -40
- package/lib/logging.js +29 -0
- package/package.json +1 -1
- package/test/lib/databases.js +3 -2
- package/test/test.js +92 -75
- package/test/test_bulk.js +1 -3
- package/test/test_findKeys.js +43 -0
- package/test/test_flush.js +61 -0
- package/test/test_metrics.js +1 -5
- package/test/test_postgres.js +3 -4
- package/test/test_setSub.js +3 -4
- package/test/test_tojson.js +1 -4
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
70
|
+
(async () => {
|
|
71
|
+
const db = new ueberdb.Database('dirty', {filename: 'var/dirty.db'});
|
|
79
72
|
await db.init();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
119
|
-
|
|
109
|
+
(async () => {
|
|
110
|
+
await db.set(key, {prop1: {prop2: ['value']}});
|
|
120
111
|
|
|
121
|
-
db.getSub(key, ['prop1', 'prop2', '0']
|
|
122
|
-
|
|
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']
|
|
127
|
-
|
|
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']
|
|
132
|
-
|
|
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, []
|
|
137
|
-
|
|
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']
|
|
142
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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('
|
|
168
|
-
db.setSub('
|
|
151
|
+
// Equivalent to db.set('key2', {prop1: {prop2: {0: 'value'}}}):
|
|
152
|
+
await db.setSub('key2', ['prop1', 'prop2', '0'], 'value'):
|
|
169
153
|
|
|
170
|
-
|
|
171
|
-
db.setSub('key2', ['prop1', 'prop2', '0'], 'value', cb):
|
|
154
|
+
await db.set('key3', {prop1: 'value'});
|
|
172
155
|
|
|
173
|
-
db.set('key3', {prop1: 'value'
|
|
174
|
-
|
|
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'
|
|
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
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
});
|
|
203
|
+
} finally {
|
|
204
|
+
await db.close();
|
|
205
|
+
}
|
|
228
206
|
})();
|
|
229
207
|
```
|
|
230
208
|
|
package/databases/couch_db.js
CHANGED
|
@@ -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
|
-
|
|
44
|
-
const settings = this.settings;
|
|
45
|
-
let client = null;
|
|
46
|
-
let db = null;
|
|
36
|
+
get isAsync() { return true; }
|
|
47
37
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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(
|
|
199
|
-
|
|
137
|
+
async close() {
|
|
138
|
+
this.db = null;
|
|
139
|
+
if (this.agent) this.agent.destroy();
|
|
140
|
+
this.agent = null;
|
|
200
141
|
}
|
|
201
142
|
};
|