hide-a-bed 3.0.1 → 4.0.0
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/README.md +104 -0
- package/cjs/impl/bulk.cjs +55 -6
- package/cjs/impl/crud.cjs +57 -7
- package/cjs/impl/errors.cjs +63 -0
- package/cjs/impl/logger.cjs +61 -0
- package/cjs/impl/patch.cjs +10 -5
- package/cjs/impl/query.cjs +11 -1
- package/cjs/impl/retry.cjs +54 -0
- package/cjs/impl/stream.cjs +48 -7
- package/cjs/index.cjs +26 -2
- package/cjs/schema/bind.cjs +40 -0
- package/cjs/schema/bulk.cjs +14 -0
- package/cjs/schema/config.cjs +20 -1
- package/cjs/schema/crud.cjs +9 -1
- package/cjs/schema/patch.cjs +6 -6
- package/cjs/schema/query.cjs +28 -19
- package/cjs/schema/stream.cjs +42 -0
- package/impl/bulk.d.mts.map +1 -1
- package/impl/bulk.mjs +58 -6
- package/impl/crud.d.mts +4 -9
- package/impl/crud.d.mts.map +1 -1
- package/impl/crud.mjs +67 -6
- package/impl/errors.d.mts +35 -0
- package/impl/errors.d.mts.map +1 -0
- package/impl/errors.mjs +53 -0
- package/impl/logger.d.mts +32 -0
- package/impl/logger.d.mts.map +1 -0
- package/impl/logger.mjs +59 -0
- package/impl/patch.d.mts +3 -1
- package/impl/patch.d.mts.map +1 -1
- package/impl/patch.mjs +36 -4
- package/impl/query.d.mts +66 -0
- package/impl/query.d.mts.map +1 -1
- package/impl/query.mjs +38 -2
- package/impl/retry.d.mts +2 -0
- package/impl/retry.d.mts.map +1 -0
- package/impl/retry.mjs +39 -0
- package/impl/stream.d.mts +2 -1
- package/impl/stream.d.mts.map +1 -1
- package/impl/stream.mjs +72 -17
- package/index.d.mts +8 -3
- package/index.d.mts.map +1 -1
- package/index.mjs +32 -3
- package/package.json +1 -1
- package/schema/bind.d.mts +470 -0
- package/schema/bind.d.mts.map +1 -0
- package/schema/bind.mjs +21 -0
- package/schema/bulk.d.mts +258 -0
- package/schema/bulk.d.mts.map +1 -1
- package/schema/bulk.mjs +17 -0
- package/schema/config.d.mts +67 -0
- package/schema/config.d.mts.map +1 -1
- package/schema/config.mjs +22 -1
- package/schema/crud.d.mts +177 -0
- package/schema/crud.d.mts.map +1 -1
- package/schema/crud.mjs +12 -0
- package/schema/patch.d.mts +88 -19
- package/schema/patch.d.mts.map +1 -1
- package/schema/patch.mjs +10 -6
- package/schema/query.d.mts +211 -0
- package/schema/query.d.mts.map +1 -1
- package/schema/query.mjs +30 -18
- package/schema/stream.d.mts +193 -0
- package/schema/stream.d.mts.map +1 -0
- package/schema/stream.mjs +23 -0
- package/build/build.mjs +0 -16
- package/build/build.rewrite-imports.mjs +0 -14
package/README.md
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
API
|
|
2
2
|
-------------
|
|
3
3
|
|
|
4
|
+
### Setup
|
|
5
|
+
|
|
6
|
+
Depending on your environment, use import or require
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
import { get, put, patch, remove, bulkSave, bulkGet, bulkRemove, query } from 'hide-a-bed'
|
|
10
|
+
```
|
|
11
|
+
```
|
|
12
|
+
const { get, put, patch, remove, bulkSave, bulkGet, bulkRemove, query } = require('hide-a-bed')
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Config
|
|
16
|
+
|
|
17
|
+
Anywhere you see a config, it is an object with the following setup
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
{ couch: 'https://username:pass@the.couch.url.com:5984' }
|
|
21
|
+
```
|
|
22
|
+
Couch get is weird. We have chosen to return ```undefined``` if the doc is not found. All other things throw. If you want
|
|
23
|
+
not_found to also throw an exception, add the following to your config:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
{ throwOnGetNotFound: true, couch: '...' }
|
|
27
|
+
```
|
|
28
|
+
|
|
4
29
|
### Document Operations
|
|
5
30
|
|
|
31
|
+
|
|
6
32
|
#### get(config, id)
|
|
7
33
|
Get a single document by ID.
|
|
8
34
|
- `config`: Object with `couch` URL string
|
|
@@ -165,4 +191,82 @@ const result = await query(config, view, options)
|
|
|
165
191
|
// }
|
|
166
192
|
```
|
|
167
193
|
|
|
194
|
+
Logging Support
|
|
195
|
+
==============
|
|
196
|
+
|
|
197
|
+
The library supports flexible logging options that can be configured through the config object:
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
// Enable console logging (error, warn, info, debug)
|
|
201
|
+
const config = {
|
|
202
|
+
couch: 'http://localhost:5984/mydb',
|
|
203
|
+
useConsoleLogger: true
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Use a custom logger object (winston-style)
|
|
207
|
+
const config = {
|
|
208
|
+
couch: 'http://localhost:5984/mydb',
|
|
209
|
+
logger: {
|
|
210
|
+
error: (msg) => console.error(msg),
|
|
211
|
+
warn: (msg) => console.warn(msg),
|
|
212
|
+
info: (msg) => console.info(msg),
|
|
213
|
+
debug: (msg) => console.debug(msg)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Use a simple function logger
|
|
218
|
+
const config = {
|
|
219
|
+
couch: 'http://localhost:5984/mydb',
|
|
220
|
+
logger: (level, ...args) => console.log(level, ...args)
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The logger will track operations including:
|
|
225
|
+
- Document operations (get, put, patch)
|
|
226
|
+
- Bulk operations
|
|
227
|
+
- View queries
|
|
228
|
+
- Streaming operations
|
|
229
|
+
- Retries and error handling
|
|
230
|
+
|
|
231
|
+
Each operation logs appropriate information at these levels:
|
|
232
|
+
- error: Fatal/unrecoverable errors
|
|
233
|
+
- warn: Retryable errors, conflicts
|
|
234
|
+
- info: Operation start/completion
|
|
235
|
+
- debug: Detailed operation information
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
Streaming Support
|
|
239
|
+
=================
|
|
240
|
+
|
|
241
|
+
Want to stream data from couch? You can with queryStream. It looks identical to query, except you add an extra 'onRow' function
|
|
242
|
+
|
|
243
|
+
Here is a small hapi example of streaming data from couch to the client as ndjson.
|
|
244
|
+
We do a small transform by only streaming the doc. you can do a lot of things in the onrow function.
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
import Hapi from '@hapi/hapi';
|
|
248
|
+
import { Readable } from 'stream';
|
|
249
|
+
import { queryStream } from bindConfig(process.env)
|
|
250
|
+
const view = '_design/users/_view/by_name'
|
|
251
|
+
|
|
252
|
+
const init = async () => {
|
|
253
|
+
const server = Hapi.server({ port: 3000 })
|
|
254
|
+
server.route({
|
|
255
|
+
method: 'GET',
|
|
256
|
+
path: '/stream',
|
|
257
|
+
handler: async (request, h) => {
|
|
258
|
+
const stream = new Readable({ read() {} });
|
|
259
|
+
const onRow = ({id, key, value, doc}) => stream.push(JSON.stringify(doc) + '\n')
|
|
260
|
+
const options = { startkey: req.query.startLetter, endkey: req.query.startLetter + '|', include_docs: true}
|
|
261
|
+
await queryStream(view, options, onRow)
|
|
262
|
+
stream.push(null) // end stream
|
|
263
|
+
return h.response(stream).type('application/x-ndjson');
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
await server.start();
|
|
268
|
+
console.log(`Server running on ${server.info.uri}`);
|
|
269
|
+
}
|
|
270
|
+
init()
|
|
271
|
+
```
|
|
168
272
|
|
package/cjs/impl/bulk.cjs
CHANGED
|
@@ -35,6 +35,8 @@ __export(bulk_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(bulk_exports);
|
|
36
36
|
var import_needle = __toESM(require("needle"), 1);
|
|
37
37
|
var import_bulk = require("../schema/bulk.cjs");
|
|
38
|
+
var import_errors = require("./errors.cjs");
|
|
39
|
+
var import_logger = require("./logger.cjs");
|
|
38
40
|
const opts = {
|
|
39
41
|
json: true,
|
|
40
42
|
headers: {
|
|
@@ -42,21 +44,65 @@ const opts = {
|
|
|
42
44
|
}
|
|
43
45
|
};
|
|
44
46
|
const bulkSave = import_bulk.BulkSave.implement(async (config, docs) => {
|
|
45
|
-
|
|
46
|
-
if (!docs
|
|
47
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
48
|
+
if (!docs) {
|
|
49
|
+
logger.warn("bulkSave called with no docs");
|
|
50
|
+
return { ok: false, error: "noDocs", reason: "no docs provided" };
|
|
51
|
+
}
|
|
52
|
+
if (!docs.length) {
|
|
53
|
+
logger.warn("bulkSave called with empty docs array");
|
|
54
|
+
return { ok: false, error: "noDocs", reason: "no docs provided" };
|
|
55
|
+
}
|
|
56
|
+
logger.info(`Starting bulk save of ${docs.length} documents`);
|
|
47
57
|
const url = `${config.couch}/_bulk_docs`;
|
|
48
58
|
const body = { docs };
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
let resp;
|
|
60
|
+
try {
|
|
61
|
+
resp = await (0, import_needle.default)("post", url, body, opts);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
logger.error("Network error during bulk save:", err);
|
|
64
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
65
|
+
}
|
|
66
|
+
if (!resp) {
|
|
67
|
+
logger.error("No response received from bulk save request");
|
|
68
|
+
throw new import_errors.RetryableError("no response", 503);
|
|
69
|
+
}
|
|
70
|
+
if (import_errors.RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
71
|
+
logger.warn(`Retryable status code received: ${resp.statusCode}`);
|
|
72
|
+
throw new import_errors.RetryableError("retryable error during bulk save", resp.statusCode);
|
|
73
|
+
}
|
|
74
|
+
if (resp.statusCode !== 201) {
|
|
75
|
+
logger.error(`Unexpected status code: ${resp.statusCode}`);
|
|
76
|
+
throw new Error("could not save");
|
|
77
|
+
}
|
|
51
78
|
const results = resp?.body || [];
|
|
52
79
|
return results;
|
|
53
80
|
});
|
|
54
81
|
const bulkGet = import_bulk.BulkGet.implement(async (config, ids) => {
|
|
82
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
55
83
|
const keys = ids;
|
|
84
|
+
logger.info(`Starting bulk get for ${keys.length} documents`);
|
|
56
85
|
const url = `${config.couch}/_all_docs?include_docs=true`;
|
|
57
86
|
const body = { keys };
|
|
58
|
-
|
|
59
|
-
|
|
87
|
+
let resp;
|
|
88
|
+
try {
|
|
89
|
+
resp = await (0, import_needle.default)("post", url, body, opts);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
logger.error("Network error during bulk get:", err);
|
|
92
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
93
|
+
}
|
|
94
|
+
if (!resp) {
|
|
95
|
+
logger.error("No response received from bulk get request");
|
|
96
|
+
throw new import_errors.RetryableError("no response", 503);
|
|
97
|
+
}
|
|
98
|
+
if (import_errors.RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
99
|
+
logger.warn(`Retryable status code received: ${resp.statusCode}`);
|
|
100
|
+
throw new import_errors.RetryableError("retryable error during bulk get", resp.statusCode);
|
|
101
|
+
}
|
|
102
|
+
if (resp.statusCode !== 200) {
|
|
103
|
+
logger.error(`Unexpected status code: ${resp.statusCode}`);
|
|
104
|
+
throw new Error("could not fetch");
|
|
105
|
+
}
|
|
60
106
|
const rows = resp?.body?.rows || [];
|
|
61
107
|
const docs = [];
|
|
62
108
|
rows.forEach((r) => {
|
|
@@ -66,9 +112,12 @@ const bulkGet = import_bulk.BulkGet.implement(async (config, ids) => {
|
|
|
66
112
|
const doc = r.doc;
|
|
67
113
|
docs.push(doc);
|
|
68
114
|
});
|
|
115
|
+
logger.info(`Successfully retrieved ${docs.length} documents`);
|
|
69
116
|
return docs;
|
|
70
117
|
});
|
|
71
118
|
const bulkRemove = import_bulk.BulkRemove.implement(async (config, ids) => {
|
|
119
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
120
|
+
logger.info(`Starting bulk remove for ${ids.length} documents`);
|
|
72
121
|
const docs = await bulkGet(config, ids);
|
|
73
122
|
docs.forEach((d) => {
|
|
74
123
|
d._deleted = true;
|
package/cjs/impl/crud.cjs
CHANGED
|
@@ -34,6 +34,8 @@ __export(crud_exports, {
|
|
|
34
34
|
module.exports = __toCommonJS(crud_exports);
|
|
35
35
|
var import_needle = __toESM(require("needle"), 1);
|
|
36
36
|
var import_crud = require("../schema/crud.cjs");
|
|
37
|
+
var import_errors = require("./errors.cjs");
|
|
38
|
+
var import_logger = require("./logger.cjs");
|
|
37
39
|
const opts = {
|
|
38
40
|
json: true,
|
|
39
41
|
headers: {
|
|
@@ -41,24 +43,72 @@ const opts = {
|
|
|
41
43
|
}
|
|
42
44
|
};
|
|
43
45
|
const get = import_crud.CouchGet.implement(async (config, id) => {
|
|
46
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
44
47
|
const url = `${config.couch}/${id}`;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
logger.info(`Getting document with id: ${id}`);
|
|
49
|
+
try {
|
|
50
|
+
const resp = await (0, import_needle.default)("get", url, opts);
|
|
51
|
+
if (!resp) {
|
|
52
|
+
logger.error("No response received from get request");
|
|
53
|
+
throw new import_errors.RetryableError("no response", 503);
|
|
54
|
+
}
|
|
55
|
+
if (resp.statusCode === 404) {
|
|
56
|
+
logger.debug(`Document not found: ${id}`);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const result = resp?.body || {};
|
|
60
|
+
if (resp.statusCode === 404) {
|
|
61
|
+
if (config.throwOnGetNotFound) {
|
|
62
|
+
logger.warn(`Document not found (throwing error): ${id}`);
|
|
63
|
+
throw new Error(result.reason || "not_found");
|
|
64
|
+
} else {
|
|
65
|
+
logger.debug(`Document not found (returning undefined): ${id}`);
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (import_errors.RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
70
|
+
logger.warn(`Retryable status code received: ${resp.statusCode}`);
|
|
71
|
+
throw new import_errors.RetryableError(result.reason || "retryable error", resp.statusCode);
|
|
72
|
+
}
|
|
73
|
+
if (resp.statusCode !== 200) {
|
|
74
|
+
logger.error(`Unexpected status code: ${resp.statusCode}`);
|
|
75
|
+
throw new Error(result.reason || "failed");
|
|
76
|
+
}
|
|
77
|
+
logger.info(`Successfully retrieved document: ${id}`);
|
|
78
|
+
return result;
|
|
79
|
+
} catch (err) {
|
|
80
|
+
logger.error("Error during get operation:", err);
|
|
81
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
50
82
|
}
|
|
51
|
-
return result;
|
|
52
83
|
});
|
|
53
84
|
const put = import_crud.CouchPut.implement(async (config, doc) => {
|
|
85
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
54
86
|
const url = `${config.couch}/${doc._id}`;
|
|
55
87
|
const body = doc;
|
|
56
|
-
|
|
88
|
+
logger.info(`Putting document with id: ${doc._id}`);
|
|
89
|
+
let resp;
|
|
90
|
+
try {
|
|
91
|
+
resp = await (0, import_needle.default)("put", url, body, opts);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
logger.error("Error during put operation:", err);
|
|
94
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
95
|
+
}
|
|
96
|
+
if (!resp) {
|
|
97
|
+
logger.error("No response received from put request");
|
|
98
|
+
throw new import_errors.RetryableError("no response", 503);
|
|
99
|
+
}
|
|
57
100
|
const result = resp?.body || {};
|
|
58
101
|
result.statusCode = resp.statusCode;
|
|
59
102
|
if (resp.statusCode === 409) {
|
|
103
|
+
logger.warn(`Conflict detected for document: ${doc._id}`);
|
|
60
104
|
result.ok = false;
|
|
61
105
|
result.error = "conflict";
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
if (import_errors.RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
109
|
+
logger.warn(`Retryable status code received: ${resp.statusCode}`);
|
|
110
|
+
throw new import_errors.RetryableError(result.reason || "retryable error", resp.statusCode);
|
|
62
111
|
}
|
|
112
|
+
logger.info(`Successfully saved document: ${doc._id}`);
|
|
63
113
|
return result;
|
|
64
114
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var errors_exports = {};
|
|
20
|
+
__export(errors_exports, {
|
|
21
|
+
RetryableError: () => RetryableError
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(errors_exports);
|
|
24
|
+
class RetryableError extends Error {
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} message - The error message
|
|
27
|
+
* @param {number|undefined} statusCode - The HTTP status code
|
|
28
|
+
*/
|
|
29
|
+
constructor(message, statusCode) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "RetryableError";
|
|
32
|
+
this.statusCode = statusCode;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* @param {number|undefined} statusCode - The HTTP status code to check
|
|
36
|
+
* @returns {boolean} Whether the status code is retryable
|
|
37
|
+
*/
|
|
38
|
+
static isRetryableStatusCode(statusCode) {
|
|
39
|
+
if (statusCode === void 0) return false;
|
|
40
|
+
return [408, 429, 500, 502, 503, 504].includes(statusCode);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @param {NetworkError | unknown} err - The network error to handle
|
|
44
|
+
* @throws {RetryableError} If the error is retryable
|
|
45
|
+
* @throws {Error} If the error is not retryable
|
|
46
|
+
*/
|
|
47
|
+
static handleNetworkError(err) {
|
|
48
|
+
const networkErrors = {
|
|
49
|
+
ECONNREFUSED: 503,
|
|
50
|
+
ECONNRESET: 503,
|
|
51
|
+
ETIMEDOUT: 503,
|
|
52
|
+
ENETUNREACH: 503,
|
|
53
|
+
ENOTFOUND: 503,
|
|
54
|
+
EPIPE: 503,
|
|
55
|
+
EHOSTUNREACH: 503,
|
|
56
|
+
ESOCKETTIMEDOUT: 503
|
|
57
|
+
};
|
|
58
|
+
if (typeof err === "object" && err !== null && "code" in err && typeof err.code === "string" && networkErrors[err.code]) {
|
|
59
|
+
throw new RetryableError(`Network error: ${err.code}`, networkErrors[err.code]);
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var logger_exports = {};
|
|
20
|
+
__export(logger_exports, {
|
|
21
|
+
createLogger: () => createLogger
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(logger_exports);
|
|
24
|
+
function createLogger(config) {
|
|
25
|
+
if (config._normalizedLogger) {
|
|
26
|
+
return config._normalizedLogger;
|
|
27
|
+
}
|
|
28
|
+
if (!config.logger) {
|
|
29
|
+
config._normalizedLogger = {
|
|
30
|
+
error: () => {
|
|
31
|
+
},
|
|
32
|
+
warn: () => {
|
|
33
|
+
},
|
|
34
|
+
info: () => {
|
|
35
|
+
},
|
|
36
|
+
debug: () => {
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return config._normalizedLogger;
|
|
40
|
+
}
|
|
41
|
+
if (typeof config.logger === "function") {
|
|
42
|
+
config._normalizedLogger = {
|
|
43
|
+
error: (...args) => config.logger("error", ...args),
|
|
44
|
+
warn: (...args) => config.logger("warn", ...args),
|
|
45
|
+
info: (...args) => config.logger("info", ...args),
|
|
46
|
+
debug: (...args) => config.logger("debug", ...args)
|
|
47
|
+
};
|
|
48
|
+
return config._normalizedLogger;
|
|
49
|
+
}
|
|
50
|
+
config._normalizedLogger = {
|
|
51
|
+
error: config.logger.error || (() => {
|
|
52
|
+
}),
|
|
53
|
+
warn: config.logger.warn || (() => {
|
|
54
|
+
}),
|
|
55
|
+
info: config.logger.info || (() => {
|
|
56
|
+
}),
|
|
57
|
+
debug: config.logger.debug || (() => {
|
|
58
|
+
})
|
|
59
|
+
};
|
|
60
|
+
return config._normalizedLogger;
|
|
61
|
+
}
|
package/cjs/impl/patch.cjs
CHANGED
|
@@ -18,19 +18,21 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var patch_exports = {};
|
|
20
20
|
__export(patch_exports, {
|
|
21
|
-
patch: () => patch
|
|
21
|
+
patch: () => patch,
|
|
22
|
+
sleep: () => sleep
|
|
22
23
|
});
|
|
23
24
|
module.exports = __toCommonJS(patch_exports);
|
|
24
25
|
var import_crud = require("./crud.cjs");
|
|
25
26
|
var import_patch = require("../schema/patch.cjs");
|
|
26
27
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
27
|
-
const patch = import_patch.
|
|
28
|
-
const maxRetries = config.
|
|
29
|
-
|
|
28
|
+
const patch = import_patch.Patch.implement(async (config, id, properties) => {
|
|
29
|
+
const maxRetries = config.maxRetries || 5;
|
|
30
|
+
let delay = config.initialDelay || 1e3;
|
|
30
31
|
let attempts = 0;
|
|
31
32
|
while (attempts <= maxRetries) {
|
|
32
33
|
try {
|
|
33
34
|
const doc = await (0, import_crud.get)(config, id);
|
|
35
|
+
if (!doc) return { ok: false, statusCode: 404, error: "not_found" };
|
|
34
36
|
const updatedDoc = { ...doc, ...properties };
|
|
35
37
|
const result = await (0, import_crud.put)(config, updatedDoc);
|
|
36
38
|
if (result.ok) {
|
|
@@ -41,10 +43,13 @@ const patch = import_patch.CouchPatch.implement(async (config, id, properties) =
|
|
|
41
43
|
throw new Error(`Failed to patch after ${maxRetries} attempts`);
|
|
42
44
|
}
|
|
43
45
|
await sleep(delay);
|
|
46
|
+
delay *= config.backoffFactor;
|
|
44
47
|
} catch (err) {
|
|
48
|
+
if (err.message !== "not_found") return { ok: false, statusCode: 404, error: "not_found" };
|
|
45
49
|
attempts++;
|
|
46
50
|
if (attempts > maxRetries) {
|
|
47
|
-
|
|
51
|
+
const error = `Failed to patch after ${maxRetries} attempts: ${err.message}`;
|
|
52
|
+
return { ok: false, statusCode: 500, error };
|
|
48
53
|
}
|
|
49
54
|
await sleep(delay);
|
|
50
55
|
}
|
package/cjs/impl/query.cjs
CHANGED
|
@@ -35,6 +35,7 @@ module.exports = __toCommonJS(query_exports);
|
|
|
35
35
|
var import_zod = require("zod");
|
|
36
36
|
var import_needle = __toESM(require("needle"), 1);
|
|
37
37
|
var import_query = require("../schema/query.cjs");
|
|
38
|
+
var import_errors = require("./errors.cjs");
|
|
38
39
|
var import_lodash = __toESM(require("lodash"), 1);
|
|
39
40
|
const { includes } = import_lodash.default;
|
|
40
41
|
const query = import_query.SimpleViewQuery.implement(async (config, view, options) => {
|
|
@@ -46,8 +47,17 @@ const query = import_query.SimpleViewQuery.implement(async (config, view, option
|
|
|
46
47
|
}
|
|
47
48
|
};
|
|
48
49
|
const url = `${config.couch}/${view}?${qs.toString()}`;
|
|
49
|
-
|
|
50
|
+
let results;
|
|
51
|
+
try {
|
|
52
|
+
results = await (0, import_needle.default)("get", url, opts);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
55
|
+
}
|
|
56
|
+
if (!results) throw new import_errors.RetryableError("no response", 503);
|
|
50
57
|
const body = results.body;
|
|
58
|
+
if (import_errors.RetryableError.isRetryableStatusCode(results.statusCode)) {
|
|
59
|
+
throw new import_errors.RetryableError(body.error || "retryable error during query", results.statusCode);
|
|
60
|
+
}
|
|
51
61
|
if (body.error) throw new Error(body.error);
|
|
52
62
|
return body;
|
|
53
63
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var retry_exports = {};
|
|
20
|
+
__export(retry_exports, {
|
|
21
|
+
withRetry: () => withRetry
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(retry_exports);
|
|
24
|
+
var import_errors = require("./errors.cjs");
|
|
25
|
+
var import_patch = require("./patch.cjs");
|
|
26
|
+
function withRetry(fn, options = {}) {
|
|
27
|
+
const {
|
|
28
|
+
maxRetries = 3,
|
|
29
|
+
initialDelay = 1e3,
|
|
30
|
+
// 1 second
|
|
31
|
+
backoffFactor = 2
|
|
32
|
+
// exponential backoff multiplier
|
|
33
|
+
} = options;
|
|
34
|
+
return async (...args) => {
|
|
35
|
+
let lastError;
|
|
36
|
+
let delay = initialDelay;
|
|
37
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
38
|
+
try {
|
|
39
|
+
return await fn(...args);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
lastError = error;
|
|
42
|
+
if (!(error instanceof import_errors.RetryableError)) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
if (attempt === maxRetries) {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
await (0, import_patch.sleep)(delay);
|
|
49
|
+
delay *= backoffFactor;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw lastError;
|
|
53
|
+
};
|
|
54
|
+
}
|
package/cjs/impl/stream.cjs
CHANGED
|
@@ -33,17 +33,58 @@ __export(stream_exports, {
|
|
|
33
33
|
module.exports = __toCommonJS(stream_exports);
|
|
34
34
|
var import_needle = __toESM(require("needle"), 1);
|
|
35
35
|
var import_query = require("./query.cjs");
|
|
36
|
+
var import_errors = require("./errors.cjs");
|
|
36
37
|
var import_JSONStream = __toESM(require("JSONStream"), 1);
|
|
37
|
-
const queryStream = (config, view, options) => new Promise((resolve, reject) => {
|
|
38
|
+
const queryStream = (config, view, options, onRow) => new Promise((resolve, reject) => {
|
|
38
39
|
if (!options) options = {};
|
|
39
|
-
const
|
|
40
|
-
const qs = (0, import_query.queryString)(rest, ["key", "startkey", "endkey", "reduce", "group", "group_level", "stale", "limit"]);
|
|
40
|
+
const qs = (0, import_query.queryString)(options, ["key", "startkey", "endkey", "reduce", "group", "group_level", "stale", "limit"]);
|
|
41
41
|
const url = `${config.couch}/${view}?${qs.toString()}`;
|
|
42
|
+
const opts = {
|
|
43
|
+
json: true,
|
|
44
|
+
headers: {
|
|
45
|
+
"Content-Type": "application/json"
|
|
46
|
+
},
|
|
47
|
+
parse_response: false
|
|
48
|
+
// Keep as stream
|
|
49
|
+
};
|
|
42
50
|
const streamer = import_JSONStream.default.parse("rows.*");
|
|
43
51
|
streamer.on("data", onRow);
|
|
44
|
-
streamer.on(
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
streamer.on(
|
|
53
|
+
"error",
|
|
54
|
+
/** @param {Error} err */
|
|
55
|
+
(err) => {
|
|
56
|
+
reject(new Error(`Stream parsing error: ${err.message}`));
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
streamer.on(
|
|
60
|
+
"done",
|
|
61
|
+
/** @param {Error|null} err */
|
|
62
|
+
(err) => {
|
|
63
|
+
try {
|
|
64
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
reject(e);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
streamer.on("end", () => {
|
|
71
|
+
resolve(void 0);
|
|
47
72
|
});
|
|
48
|
-
import_needle.default.get(url)
|
|
73
|
+
const req = import_needle.default.get(url, opts);
|
|
74
|
+
req.on("response", (response) => {
|
|
75
|
+
if (import_errors.RetryableError.isRetryableStatusCode(response.statusCode)) {
|
|
76
|
+
reject(new import_errors.RetryableError("retryable error during stream query", response.statusCode));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
req.on("error", (err) => {
|
|
81
|
+
try {
|
|
82
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
83
|
+
} catch (retryErr) {
|
|
84
|
+
reject(retryErr);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
reject(err);
|
|
88
|
+
});
|
|
89
|
+
req.pipe(streamer);
|
|
49
90
|
});
|
package/cjs/index.cjs
CHANGED
|
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var index_exports = {};
|
|
20
20
|
__export(index_exports, {
|
|
21
|
+
bindConfig: () => bindConfig,
|
|
21
22
|
bulkGet: () => import_bulk.bulkGet,
|
|
22
23
|
bulkRemove: () => import_bulk.bulkRemove,
|
|
23
24
|
bulkSave: () => import_bulk.bulkSave,
|
|
@@ -26,7 +27,8 @@ __export(index_exports, {
|
|
|
26
27
|
put: () => import_crud.put,
|
|
27
28
|
query: () => import_query.query,
|
|
28
29
|
queryStream: () => import_stream.queryStream,
|
|
29
|
-
schema: () => schema
|
|
30
|
+
schema: () => schema,
|
|
31
|
+
withRetry: () => import_retry.withRetry
|
|
30
32
|
});
|
|
31
33
|
module.exports = __toCommonJS(index_exports);
|
|
32
34
|
var import_bulk = require("./impl/bulk.cjs");
|
|
@@ -34,21 +36,43 @@ var import_crud = require("./impl/crud.cjs");
|
|
|
34
36
|
var import_patch = require("./impl/patch.cjs");
|
|
35
37
|
var import_query = require("./impl/query.cjs");
|
|
36
38
|
var import_stream = require("./impl/stream.cjs");
|
|
39
|
+
var import_retry = require("./impl/retry.cjs");
|
|
37
40
|
var import_bulk2 = require("./schema/bulk.cjs");
|
|
38
41
|
var import_config = require("./schema/config.cjs");
|
|
39
42
|
var import_query2 = require("./schema/query.cjs");
|
|
43
|
+
var import_stream2 = require("./schema/stream.cjs");
|
|
40
44
|
var import_patch2 = require("./schema/patch.cjs");
|
|
41
45
|
var import_crud2 = require("./schema/crud.cjs");
|
|
46
|
+
var import_bind = require("./schema/bind.cjs");
|
|
42
47
|
const schema = {
|
|
43
48
|
CouchConfig: import_config.CouchConfig,
|
|
44
49
|
SimpleViewQuery: import_query2.SimpleViewQuery,
|
|
45
50
|
SimpleViewQueryResponse: import_query2.SimpleViewQueryResponse,
|
|
51
|
+
SimpleViewQueryStream: import_stream2.SimpleViewQueryStream,
|
|
52
|
+
OnRow: import_stream2.OnRow,
|
|
46
53
|
BulkSave: import_bulk2.BulkSave,
|
|
47
54
|
BulkGet: import_bulk2.BulkGet,
|
|
48
55
|
CouchGet: import_crud2.CouchGet,
|
|
49
56
|
CouchPut: import_crud2.CouchPut,
|
|
50
57
|
CouchDoc: import_crud2.CouchDoc,
|
|
51
58
|
CouchDocResponse: import_crud2.CouchDocResponse,
|
|
52
|
-
PatchConfig: import_patch2.PatchConfig,
|
|
53
59
|
Patch: import_patch2.Patch
|
|
54
60
|
};
|
|
61
|
+
const bindConfig = import_bind.Bind.implement((config) => {
|
|
62
|
+
const retryOptions = {
|
|
63
|
+
maxRetries: config.maxRetries ?? 10,
|
|
64
|
+
initialDelay: config.initialDelay ?? 1e3,
|
|
65
|
+
backoffFactor: config.backoffFactor ?? 2
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
get: config.bindWithRetry ? (0, import_retry.withRetry)(import_crud.get.bind(null, config), retryOptions) : import_crud.get.bind(null, config),
|
|
69
|
+
put: config.bindWithRetry ? (0, import_retry.withRetry)(import_crud.put.bind(null, config), retryOptions) : import_crud.put.bind(null, config),
|
|
70
|
+
patch: import_patch.patch.bind(null, config),
|
|
71
|
+
// patch not included in retry
|
|
72
|
+
bulkGet: config.bindWithRetry ? (0, import_retry.withRetry)(import_bulk.bulkGet.bind(null, config), retryOptions) : import_bulk.bulkGet.bind(null, config),
|
|
73
|
+
bulkSave: config.bindWithRetry ? (0, import_retry.withRetry)(import_bulk.bulkSave.bind(null, config), retryOptions) : import_bulk.bulkSave.bind(null, config),
|
|
74
|
+
bulkRemove: config.bindWithRetry ? (0, import_retry.withRetry)(import_bulk.bulkRemove.bind(null, config), retryOptions) : import_bulk.bulkRemove.bind(null, config),
|
|
75
|
+
query: config.bindWithRetry ? (0, import_retry.withRetry)(import_query.query.bind(null, config), retryOptions) : import_query.query.bind(null, config),
|
|
76
|
+
queryStream: config.bindWithRetry ? (0, import_retry.withRetry)(import_stream.queryStream.bind(null, config), retryOptions) : import_stream.queryStream.bind(null, config)
|
|
77
|
+
};
|
|
78
|
+
});
|