hide-a-bed 5.0.0 → 5.0.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/README.md +96 -93
- package/cjs/impl/bulk.cjs +267 -0
- package/cjs/impl/changes.cjs +67 -0
- package/cjs/impl/crud.cjs +121 -0
- package/cjs/impl/errors.cjs +75 -0
- package/cjs/impl/logger.cjs +70 -0
- package/cjs/impl/patch.cjs +95 -0
- package/cjs/impl/query.cjs +116 -0
- package/cjs/impl/queryBuilder.cjs +99 -0
- package/cjs/impl/retry.cjs +54 -0
- package/cjs/impl/stream.cjs +121 -0
- package/cjs/impl/sugar/lock.cjs +81 -0
- package/cjs/impl/sugar/watch.cjs +159 -0
- package/cjs/impl/trackedEmitter.cjs +54 -0
- package/cjs/impl/transactionErrors.cjs +70 -0
- package/cjs/index.cjs +115 -0
- package/cjs/integration/changes.cjs +76 -0
- package/cjs/integration/disconnect-watch.cjs +52 -0
- package/cjs/integration/watch.cjs +59 -0
- package/cjs/schema/bind.cjs +51 -0
- package/cjs/schema/bulk.cjs +88 -0
- package/cjs/schema/changes.cjs +68 -0
- package/cjs/schema/config.cjs +48 -0
- package/cjs/schema/crud.cjs +77 -0
- package/cjs/schema/patch.cjs +53 -0
- package/cjs/schema/query.cjs +62 -0
- package/cjs/schema/stream.cjs +42 -0
- package/cjs/schema/sugar/lock.cjs +59 -0
- package/cjs/schema/sugar/watch.cjs +42 -0
- package/impl/bulk.d.mts +11 -0
- package/impl/bulk.d.mts.map +1 -0
- package/impl/changes.d.mts +12 -0
- package/impl/changes.d.mts.map +1 -0
- package/impl/changes.mjs +2 -7
- package/impl/crud.d.mts +7 -0
- package/impl/crud.d.mts.map +1 -0
- package/impl/errors.d.mts +43 -0
- package/impl/errors.d.mts.map +1 -0
- package/impl/errors.mjs +1 -1
- package/impl/logger.d.mts +32 -0
- package/impl/logger.d.mts.map +1 -0
- package/impl/patch.d.mts +6 -0
- package/impl/patch.d.mts.map +1 -0
- package/impl/query.d.mts +195 -0
- package/impl/query.d.mts.map +1 -0
- package/impl/query.mjs +14 -6
- package/impl/queryBuilder.d.mts +94 -0
- package/impl/queryBuilder.d.mts.map +1 -0
- package/impl/retry.d.mts +2 -0
- package/impl/retry.d.mts.map +1 -0
- package/impl/stream.d.mts +3 -0
- package/impl/stream.d.mts.map +1 -0
- package/impl/stream.mjs +2 -2
- package/impl/sugar/lock.d.mts +5 -0
- package/impl/sugar/lock.d.mts.map +1 -0
- package/impl/sugar/lock.mjs +3 -3
- package/impl/sugar/watch.d.mts +34 -0
- package/impl/sugar/watch.d.mts.map +1 -0
- package/impl/sugar/watch.mjs +66 -66
- package/impl/trackedEmitter.d.mts +8 -0
- package/impl/trackedEmitter.d.mts.map +1 -0
- package/impl/transactionErrors.d.mts +57 -0
- package/impl/transactionErrors.d.mts.map +1 -0
- package/index.d.mts +74 -0
- package/index.d.mts.map +1 -0
- package/package.json +1 -1
- package/schema/bind.d.mts +922 -0
- package/schema/bind.d.mts.map +1 -0
- package/schema/bulk.d.mts +910 -0
- package/schema/bulk.d.mts.map +1 -0
- package/schema/changes.d.mts +191 -0
- package/schema/changes.d.mts.map +1 -0
- package/schema/changes.mjs +1 -1
- package/schema/config.d.mts +79 -0
- package/schema/config.d.mts.map +1 -0
- package/schema/crud.d.mts +491 -0
- package/schema/crud.d.mts.map +1 -0
- package/schema/patch.d.mts +255 -0
- package/schema/patch.d.mts.map +1 -0
- package/schema/query.d.mts +406 -0
- package/schema/query.d.mts.map +1 -0
- package/schema/stream.d.mts +211 -0
- package/schema/stream.d.mts.map +1 -0
- package/schema/sugar/lock.d.mts +238 -0
- package/schema/sugar/lock.d.mts.map +1 -0
- package/schema/sugar/watch.d.mts +127 -0
- package/schema/sugar/watch.d.mts.map +1 -0
package/README.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
API
|
|
2
|
-
|
|
1
|
+
### API Quick Reference
|
|
2
|
+
|
|
3
|
+
🍭 denotes a *Sugar* api - helps makes some tasks sweet and easy, but may hide some complexities you might want to deal with.
|
|
4
|
+
|
|
5
|
+
| Document Operations | Bulk Operations | View Operations | Changes Feed |
|
|
6
|
+
|-------------------|-----------------|-----------------|-----------------|
|
|
7
|
+
| [`get()`](#get) | [`bulkGet()`](#bulkget) | [`query()`](#query) | [`changes()`](#changes) |
|
|
8
|
+
| [`put()`](#put) | [`bulkSave()`](#bulksave) | [`queryStream()`](#querystream) | [`watchDocs()`](#watchDocs) 🍭 |
|
|
9
|
+
| [`patch()`](#patch) 🍭 | [`bulkRemove()`](#bulkremove) | | |
|
|
10
|
+
| [`patchDangerously()`](#patchdangerously) 🍭 | [`bulkGetDictionary()`](#bulkgetdictionary) 🍭 | | |
|
|
11
|
+
| [`getAtRev()`](#getatrev) 🍭 | [`bulkSaveTransaction()`](#bulksavetransaction) 🍭 | | |
|
|
12
|
+
| [`createLock()`](#createLock) 🍭 | | | |
|
|
13
|
+
| [`removeLock()`](#removeLock) 🍭 | | | |
|
|
14
|
+
|
|
15
|
+
And some utility apis
|
|
16
|
+
|
|
17
|
+
- [`createQuery()`](#createquery) 🍭
|
|
18
|
+
- [`withRetry()`](#withretry)
|
|
19
|
+
|
|
3
20
|
|
|
4
21
|
### Setup
|
|
5
22
|
|
|
@@ -26,24 +43,6 @@ const db = bindConfig(process.env)
|
|
|
26
43
|
const doc = db.get('doc-123')
|
|
27
44
|
```
|
|
28
45
|
|
|
29
|
-
### API Quick Reference
|
|
30
|
-
|
|
31
|
-
| Document Operations | Bulk Operations | View Operations |
|
|
32
|
-
|-------------------|-----------------|-----------------|
|
|
33
|
-
| [`get()`](#get) | [`bulkGet()`](#bulkget) | [`query()`](#query) |
|
|
34
|
-
| [`put()`](#put) | [`bulkSave()`](#bulksave) | [`queryStream()`](#querystream) |
|
|
35
|
-
| [`patch()`](#patch) | [`bulkRemove()`](#bulkremove) | [`createQuery()`](#createquery) |
|
|
36
|
-
| [`patchDangerously()`](#patchdangerously) | [`bulkGetDictionary()`](#bulkgetdictionary) | |
|
|
37
|
-
| [`getAtRev()`](#getatrev) | [`bulkSaveTransaction()`](#bulksavetransaction) | |
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Some *Sugar* API helpers
|
|
41
|
-
|
|
42
|
-
- [`createLock()`](#createLock)
|
|
43
|
-
- [`removeLock()`](#removeLock)
|
|
44
|
-
- [`changes()`](#changes)
|
|
45
|
-
- [`watchDocs()`](#watchDocs)
|
|
46
|
-
|
|
47
46
|
### Document Operations
|
|
48
47
|
|
|
49
48
|
#### get
|
|
@@ -284,7 +283,7 @@ const results = await bulkRemove(config, ids)
|
|
|
284
283
|
|
|
285
284
|
#### bulkGetDictionary
|
|
286
285
|
|
|
287
|
-
Adds some convenience to bulkGet.
|
|
286
|
+
Adds some convenience to bulkGet. Organizes found and notFound documents into properties that are {id:result}. This makes it easy to deal with the results.
|
|
288
287
|
|
|
289
288
|
**Parameters:**
|
|
290
289
|
- `config`: Object with `couch` URL string
|
|
@@ -480,79 +479,9 @@ const init = async () => {
|
|
|
480
479
|
}
|
|
481
480
|
init()
|
|
482
481
|
```
|
|
483
|
-
Advanced Config Options
|
|
484
|
-
=======================
|
|
485
|
-
|
|
486
|
-
The config object supports the following properties:
|
|
487
|
-
|
|
488
|
-
| Property | Type | Default | Description |
|
|
489
|
-
|----------|------|---------|-------------|
|
|
490
|
-
| couch | string | required | The URL of the CouchDB database |
|
|
491
|
-
| throwOnGetNotFound | boolean | false | If true, throws an error when get() returns 404. If false, returns undefined |
|
|
492
|
-
| bindWithRetry | boolean | true | When using bindConfig(), adds retry logic to bound methods |
|
|
493
|
-
| maxRetries | number | 3 | Maximum number of retry attempts for retryable operations |
|
|
494
|
-
| initialDelay | number | 1000 | Initial delay in milliseconds before first retry |
|
|
495
|
-
| backoffFactor | number | 2 | Multiplier for exponential backoff between retries |
|
|
496
|
-
| useConsoleLogger | boolean | false | If true, enables console logging when no logger is provided |
|
|
497
|
-
| logger | object/function | undefined | Custom logging interface (winston-style object or function) |
|
|
498
|
-
|
|
499
|
-
Example configuration with all options:
|
|
500
|
-
```javascript
|
|
501
|
-
const config = {
|
|
502
|
-
couch: 'http://localhost:5984/mydb',
|
|
503
|
-
throwOnGetNotFound: true,
|
|
504
|
-
bindWithRetry: true,
|
|
505
|
-
maxRetries: 5,
|
|
506
|
-
initialDelay: 2000,
|
|
507
|
-
backoffFactor: 1.5,
|
|
508
|
-
useConsoleLogger: true,
|
|
509
|
-
logger: (level, ...args) => console.log(level, ...args)
|
|
510
|
-
}
|
|
511
|
-
```
|
|
512
482
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
==============
|
|
516
|
-
|
|
517
|
-
The library supports flexible logging options that can be configured through the config object:
|
|
518
|
-
|
|
519
|
-
```javascript
|
|
520
|
-
// Enable console logging (error, warn, info, debug)
|
|
521
|
-
const config = {
|
|
522
|
-
couch: 'http://localhost:5984/mydb',
|
|
523
|
-
useConsoleLogger: true
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Use a custom logger object (winston-style)
|
|
527
|
-
const config = {
|
|
528
|
-
couch: 'http://localhost:5984/mydb',
|
|
529
|
-
logger: {
|
|
530
|
-
error: (msg) => console.error(msg),
|
|
531
|
-
warn: (msg) => console.warn(msg),
|
|
532
|
-
info: (msg) => console.info(msg),
|
|
533
|
-
debug: (msg) => console.debug(msg)
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// Use a simple function logger
|
|
538
|
-
const config = {
|
|
539
|
-
couch: 'http://localhost:5984/mydb',
|
|
540
|
-
logger: (level, ...args) => console.log(level, ...args)
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
The logger will track operations including:
|
|
545
|
-
- Document operations (get, put, patch)
|
|
546
|
-
- Bulk operations
|
|
547
|
-
- View queries
|
|
548
|
-
- Streaming operations
|
|
549
|
-
- Retries and error handling
|
|
550
|
-
|
|
551
|
-
Each operation logs appropriate information at these levels:
|
|
552
|
-
- error: Fatal/unrecoverable errors
|
|
553
|
-
- warn: Retryable errors, conflicts
|
|
554
|
-
- info: Operation start/completion
|
|
555
|
-
- debug: Detailed operation information
|
|
483
|
+
Want to consume this in the browser? I'd recomment https://www.npmjs.com/package/ndjson-readablestream
|
|
484
|
+
here is a react component that consumes it https://github.com/Azure-Samples/azure-search-openai-demo/pull/532/files#diff-506debba46b93087dc46a916384e56392808bcc02a99d9291557f3e674d4ad6c
|
|
556
485
|
|
|
557
486
|
#### changes()
|
|
558
487
|
|
|
@@ -659,3 +588,77 @@ Watch specific documents for changes in real-time.
|
|
|
659
588
|
- Triggering actions when particular documents change
|
|
660
589
|
- Maintaining cached copies of frequently accessed documents
|
|
661
590
|
|
|
591
|
+
Advanced Config Options
|
|
592
|
+
=======================
|
|
593
|
+
|
|
594
|
+
The config object supports the following properties:
|
|
595
|
+
|
|
596
|
+
| Property | Type | Default | Description |
|
|
597
|
+
|----------|------|---------|-------------|
|
|
598
|
+
| couch | string | required | The URL of the CouchDB database |
|
|
599
|
+
| throwOnGetNotFound | boolean | false | If true, throws an error when get() returns 404. If false, returns undefined |
|
|
600
|
+
| bindWithRetry | boolean | true | When using bindConfig(), adds retry logic to bound methods |
|
|
601
|
+
| maxRetries | number | 3 | Maximum number of retry attempts for retryable operations |
|
|
602
|
+
| initialDelay | number | 1000 | Initial delay in milliseconds before first retry |
|
|
603
|
+
| backoffFactor | number | 2 | Multiplier for exponential backoff between retries |
|
|
604
|
+
| useConsoleLogger | boolean | false | If true, enables console logging when no logger is provided |
|
|
605
|
+
| logger | object/function | undefined | Custom logging interface (winston-style object or function) |
|
|
606
|
+
|
|
607
|
+
Example configuration with all options:
|
|
608
|
+
```javascript
|
|
609
|
+
const config = {
|
|
610
|
+
couch: 'http://localhost:5984/mydb',
|
|
611
|
+
throwOnGetNotFound: true,
|
|
612
|
+
bindWithRetry: true,
|
|
613
|
+
maxRetries: 5,
|
|
614
|
+
initialDelay: 2000,
|
|
615
|
+
backoffFactor: 1.5,
|
|
616
|
+
useConsoleLogger: true,
|
|
617
|
+
logger: (level, ...args) => console.log(level, ...args)
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
Logging Support
|
|
623
|
+
==============
|
|
624
|
+
|
|
625
|
+
The library supports flexible logging options that can be configured through the config object:
|
|
626
|
+
|
|
627
|
+
```javascript
|
|
628
|
+
// Enable console logging (error, warn, info, debug)
|
|
629
|
+
const config = {
|
|
630
|
+
couch: 'http://localhost:5984/mydb',
|
|
631
|
+
useConsoleLogger: true
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Use a custom logger object (winston-style)
|
|
635
|
+
const config = {
|
|
636
|
+
couch: 'http://localhost:5984/mydb',
|
|
637
|
+
logger: {
|
|
638
|
+
error: (msg) => console.error(msg),
|
|
639
|
+
warn: (msg) => console.warn(msg),
|
|
640
|
+
info: (msg) => console.info(msg),
|
|
641
|
+
debug: (msg) => console.debug(msg)
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Use a simple function logger
|
|
646
|
+
const config = {
|
|
647
|
+
couch: 'http://localhost:5984/mydb',
|
|
648
|
+
logger: (level, ...args) => console.log(level, ...args)
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
The logger will track operations including:
|
|
653
|
+
- Document operations (get, put, patch)
|
|
654
|
+
- Bulk operations
|
|
655
|
+
- View queries
|
|
656
|
+
- Streaming operations
|
|
657
|
+
- Retries and error handling
|
|
658
|
+
|
|
659
|
+
Each operation logs appropriate information at these levels:
|
|
660
|
+
- error: Fatal/unrecoverable errors
|
|
661
|
+
- warn: Retryable errors, conflicts
|
|
662
|
+
- info: Operation start/completion
|
|
663
|
+
- debug: Detailed operation information
|
|
664
|
+
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var bulk_exports = {};
|
|
30
|
+
__export(bulk_exports, {
|
|
31
|
+
bulkGet: () => bulkGet,
|
|
32
|
+
bulkGetDictionary: () => bulkGetDictionary,
|
|
33
|
+
bulkRemove: () => bulkRemove,
|
|
34
|
+
bulkSave: () => bulkSave,
|
|
35
|
+
bulkSaveTransaction: () => bulkSaveTransaction
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(bulk_exports);
|
|
38
|
+
var import_needle = __toESM(require("needle"), 1);
|
|
39
|
+
var import_bulk = require("../schema/bulk.cjs");
|
|
40
|
+
var import_retry = require("./retry.cjs");
|
|
41
|
+
var import_crud = require("./crud.cjs");
|
|
42
|
+
var import_errors = require("./errors.cjs");
|
|
43
|
+
var import_transactionErrors = require("./transactionErrors.cjs");
|
|
44
|
+
var import_logger = require("./logger.cjs");
|
|
45
|
+
var import_crud2 = require("../schema/crud.cjs");
|
|
46
|
+
var import_trackedEmitter = require("./trackedEmitter.cjs");
|
|
47
|
+
const opts = {
|
|
48
|
+
json: true,
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": "application/json"
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const bulkSave = import_bulk.BulkSave.implement(async (config, docs) => {
|
|
54
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
55
|
+
if (!docs) {
|
|
56
|
+
logger.warn("bulkSave called with no docs");
|
|
57
|
+
return { ok: false, error: "noDocs", reason: "no docs provided" };
|
|
58
|
+
}
|
|
59
|
+
if (!docs.length) {
|
|
60
|
+
logger.warn("bulkSave called with empty docs array");
|
|
61
|
+
return { ok: false, error: "noDocs", reason: "no docs provided" };
|
|
62
|
+
}
|
|
63
|
+
logger.info(`Starting bulk save of ${docs.length} documents`);
|
|
64
|
+
const url = `${config.couch}/_bulk_docs`;
|
|
65
|
+
const body = { docs };
|
|
66
|
+
let resp;
|
|
67
|
+
try {
|
|
68
|
+
resp = await (0, import_needle.default)("post", url, body, opts);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
logger.error("Network error during bulk save:", err);
|
|
71
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
72
|
+
}
|
|
73
|
+
if (!resp) {
|
|
74
|
+
logger.error("No response received from bulk save request");
|
|
75
|
+
throw new import_errors.RetryableError("no response", 503);
|
|
76
|
+
}
|
|
77
|
+
if (import_errors.RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
78
|
+
logger.warn(`Retryable status code received: ${resp.statusCode}`);
|
|
79
|
+
throw new import_errors.RetryableError("retryable error during bulk save", resp.statusCode);
|
|
80
|
+
}
|
|
81
|
+
if (resp.statusCode !== 201) {
|
|
82
|
+
logger.error(`Unexpected status code: ${resp.statusCode}`);
|
|
83
|
+
throw new Error("could not save");
|
|
84
|
+
}
|
|
85
|
+
const results = resp?.body || [];
|
|
86
|
+
return results;
|
|
87
|
+
});
|
|
88
|
+
const bulkGet = import_bulk.BulkGet.implement(async (config, ids) => {
|
|
89
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
90
|
+
const keys = ids;
|
|
91
|
+
logger.info(`Starting bulk get for ${keys.length} documents`);
|
|
92
|
+
const url = `${config.couch}/_all_docs?include_docs=true`;
|
|
93
|
+
const payload = { keys };
|
|
94
|
+
let resp;
|
|
95
|
+
try {
|
|
96
|
+
resp = await (0, import_needle.default)("post", url, payload, opts);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
logger.error("Network error during bulk get:", err);
|
|
99
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
100
|
+
}
|
|
101
|
+
if (!resp) {
|
|
102
|
+
logger.error("No response received from bulk get request");
|
|
103
|
+
throw new import_errors.RetryableError("no response", 503);
|
|
104
|
+
}
|
|
105
|
+
if (import_errors.RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
106
|
+
logger.warn(`Retryable status code received: ${resp.statusCode}`);
|
|
107
|
+
throw new import_errors.RetryableError("retryable error during bulk get", resp.statusCode);
|
|
108
|
+
}
|
|
109
|
+
if (resp.statusCode !== 200) {
|
|
110
|
+
logger.error(`Unexpected status code: ${resp.statusCode}`);
|
|
111
|
+
throw new Error("could not fetch");
|
|
112
|
+
}
|
|
113
|
+
const body = resp.body;
|
|
114
|
+
return body;
|
|
115
|
+
});
|
|
116
|
+
const bulkRemove = import_bulk.BulkRemove.implement(async (config, ids) => {
|
|
117
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
118
|
+
logger.info(`Starting bulk remove for ${ids.length} documents`);
|
|
119
|
+
const resp = await bulkGet(config, ids);
|
|
120
|
+
const toRemove = [];
|
|
121
|
+
resp.rows.forEach((row) => {
|
|
122
|
+
if (!row.doc) return;
|
|
123
|
+
try {
|
|
124
|
+
const d = import_crud2.CouchDoc.parse(row.doc);
|
|
125
|
+
d._deleted = true;
|
|
126
|
+
toRemove.push(d);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
logger.warn(`Invalid document structure in bulk remove: ${row.id}`, e);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return bulkSave(config, toRemove);
|
|
132
|
+
});
|
|
133
|
+
const bulkGetDictionary = import_bulk.BulkGetDictionary.implement(async (config, ids) => {
|
|
134
|
+
const resp = await bulkGet(config, ids);
|
|
135
|
+
const results = { found: {}, notFound: {} };
|
|
136
|
+
resp.rows.forEach(
|
|
137
|
+
/** @param { import('../schema/query.mjs').ViewRowSchema } row */
|
|
138
|
+
(row) => {
|
|
139
|
+
if (!row.key) return;
|
|
140
|
+
if (row.error) {
|
|
141
|
+
results.notFound[row.key] = row;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const doc = import_crud2.CouchDoc.parse(row.doc);
|
|
146
|
+
results.found[doc._id] = doc;
|
|
147
|
+
} catch (e) {
|
|
148
|
+
results.notFound[row.key] = row;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
return results;
|
|
153
|
+
});
|
|
154
|
+
const bulkSaveTransaction = import_bulk.BulkSaveTransaction.implement(async (config, transactionId, docs) => {
|
|
155
|
+
const emitter = (0, import_trackedEmitter.setupEmitter)(config);
|
|
156
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
157
|
+
const retryOptions = {
|
|
158
|
+
maxRetries: config.maxRetries ?? 10,
|
|
159
|
+
initialDelay: config.initialDelay ?? 1e3,
|
|
160
|
+
backoffFactor: config.backoffFactor ?? 2
|
|
161
|
+
};
|
|
162
|
+
const _put = config.bindWithRetry ? (0, import_retry.withRetry)(import_crud.put.bind(null, config), retryOptions) : import_crud.put.bind(null, config);
|
|
163
|
+
logger.info(`Starting bulk save transaction ${transactionId} for ${docs.length} documents`);
|
|
164
|
+
const txnDoc = {
|
|
165
|
+
_id: `txn:${transactionId}`,
|
|
166
|
+
_rev: null,
|
|
167
|
+
type: "transaction",
|
|
168
|
+
status: "pending",
|
|
169
|
+
changes: docs,
|
|
170
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
171
|
+
};
|
|
172
|
+
let txnresp = await _put(txnDoc);
|
|
173
|
+
logger.debug("Transaction document created:", txnDoc, txnresp);
|
|
174
|
+
await emitter.emit("transaction-created", { txnresp, txnDoc });
|
|
175
|
+
if (txnresp.error) {
|
|
176
|
+
throw new import_transactionErrors.TransactionSetupError("Failed to create transaction document", {
|
|
177
|
+
error: txnresp.error,
|
|
178
|
+
response: txnresp.body
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const existingDocs = await bulkGetDictionary(config, docs.map((d) => d._id));
|
|
182
|
+
logger.debug("Fetched current revisions of documents:", existingDocs);
|
|
183
|
+
await emitter.emit("transaction-revs-fetched", existingDocs);
|
|
184
|
+
const revErrors = [];
|
|
185
|
+
docs.forEach((d) => {
|
|
186
|
+
if (existingDocs.found[d._id] && existingDocs.found[d._id]._rev !== d._rev) revErrors.push(d._id);
|
|
187
|
+
if (existingDocs.notFound[d._id] && d._rev) revErrors.push(d._id);
|
|
188
|
+
});
|
|
189
|
+
if (revErrors.length > 0) {
|
|
190
|
+
throw new import_transactionErrors.TransactionVersionConflictError(revErrors);
|
|
191
|
+
}
|
|
192
|
+
logger.debug("Checked document revisions:", existingDocs);
|
|
193
|
+
await emitter.emit("transaction-revs-checked", existingDocs);
|
|
194
|
+
const providedDocsById = {};
|
|
195
|
+
docs.forEach((d) => {
|
|
196
|
+
if (!d._id) return;
|
|
197
|
+
providedDocsById[d._id] = d;
|
|
198
|
+
});
|
|
199
|
+
const newDocsToRollback = [];
|
|
200
|
+
const potentialExistingDocsToRollack = [];
|
|
201
|
+
const failedDocs = [];
|
|
202
|
+
try {
|
|
203
|
+
logger.info("Transaction started:", txnDoc);
|
|
204
|
+
await emitter.emit("transaction-started", txnDoc);
|
|
205
|
+
const results = await bulkSave(config, docs);
|
|
206
|
+
logger.info("Transaction updates applied:", results);
|
|
207
|
+
await emitter.emit("transaction-updates-applied", results);
|
|
208
|
+
results.forEach((r) => {
|
|
209
|
+
if (!r.id) return;
|
|
210
|
+
if (!r.error) {
|
|
211
|
+
if (existingDocs.notFound[r.id]) newDocsToRollback.push(r);
|
|
212
|
+
if (existingDocs.found[r.id]) potentialExistingDocsToRollack.push(r);
|
|
213
|
+
} else {
|
|
214
|
+
failedDocs.push(r);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
if (failedDocs.length > 0) {
|
|
218
|
+
throw new import_transactionErrors.TransactionBulkOperationError(failedDocs);
|
|
219
|
+
}
|
|
220
|
+
txnDoc.status = "completed";
|
|
221
|
+
txnDoc._rev = txnresp.rev;
|
|
222
|
+
txnresp = await _put(txnDoc);
|
|
223
|
+
logger.info("Transaction completed:", txnDoc);
|
|
224
|
+
await emitter.emit("transaction-completed", { txnresp, txnDoc });
|
|
225
|
+
if (txnresp.statusCode !== 201) {
|
|
226
|
+
logger.error("Failed to update transaction status to completed");
|
|
227
|
+
}
|
|
228
|
+
return results;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
logger.error("Transaction failed, attempting rollback:", error);
|
|
231
|
+
const toRollback = [];
|
|
232
|
+
potentialExistingDocsToRollack.forEach((row) => {
|
|
233
|
+
if (!row.id || !row.rev) return;
|
|
234
|
+
const doc = existingDocs.found[row.id];
|
|
235
|
+
doc._rev = row.rev;
|
|
236
|
+
toRollback.push(doc);
|
|
237
|
+
});
|
|
238
|
+
newDocsToRollback.forEach((d) => {
|
|
239
|
+
if (!d.id || !d.rev) return;
|
|
240
|
+
const before = structuredClone(providedDocsById[d.id]);
|
|
241
|
+
before._rev = d.rev;
|
|
242
|
+
before._deleted = true;
|
|
243
|
+
toRollback.push(before);
|
|
244
|
+
});
|
|
245
|
+
const bulkRollbackResult = await bulkSave(config, toRollback);
|
|
246
|
+
let status = "rolled_back";
|
|
247
|
+
bulkRollbackResult.forEach((r) => {
|
|
248
|
+
if (r.error) status = "rollback_failed";
|
|
249
|
+
});
|
|
250
|
+
logger.warn("Transaction rolled back:", { bulkRollbackResult, status });
|
|
251
|
+
await emitter.emit("transaction-rolled-back", { bulkRollbackResult, status });
|
|
252
|
+
txnDoc.status = status;
|
|
253
|
+
txnDoc._rev = txnresp.rev;
|
|
254
|
+
txnresp = await _put(txnDoc);
|
|
255
|
+
logger.warn("Transaction rollback status updated:", txnDoc);
|
|
256
|
+
await emitter.emit("transaction-rolled-back-status", { txnresp, txnDoc });
|
|
257
|
+
if (txnresp.statusCode !== 201) {
|
|
258
|
+
logger.error("Failed to update transaction status to rolled_back");
|
|
259
|
+
}
|
|
260
|
+
throw new import_transactionErrors.TransactionRollbackError(
|
|
261
|
+
"Transaction failed and rollback was unsuccessful",
|
|
262
|
+
/** @type {Error} */
|
|
263
|
+
error,
|
|
264
|
+
bulkRollbackResult
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var changes_exports = {};
|
|
30
|
+
__export(changes_exports, {
|
|
31
|
+
changes: () => changes
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(changes_exports);
|
|
34
|
+
var import_needle = __toESM(require("needle"), 1);
|
|
35
|
+
var import_events = require("events");
|
|
36
|
+
var import_changes = require("../schema/changes.cjs");
|
|
37
|
+
var import_changes_stream = __toESM(require("changes-stream"), 1);
|
|
38
|
+
const changes = import_changes.Changes.implement(async (config, onChange, options = {}) => {
|
|
39
|
+
const emitter = new import_events.EventEmitter();
|
|
40
|
+
options.db = config.couch;
|
|
41
|
+
if (options.since && options.since === "now") {
|
|
42
|
+
const opts = {
|
|
43
|
+
json: true,
|
|
44
|
+
headers: {
|
|
45
|
+
"Content-Type": "application/json"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const resp = await (0, import_needle.default)("get", config.couch, opts);
|
|
49
|
+
options.since = resp.body.update_seq;
|
|
50
|
+
}
|
|
51
|
+
const changes2 = (0, import_changes_stream.default)(options);
|
|
52
|
+
changes2.on("readable", () => {
|
|
53
|
+
const change = changes2.read();
|
|
54
|
+
if (change.results && Array.isArray(change.results)) {
|
|
55
|
+
change.results.forEach((c) => emitter.emit("change", c));
|
|
56
|
+
} else emitter.emit("change", change);
|
|
57
|
+
});
|
|
58
|
+
emitter.on("change", onChange);
|
|
59
|
+
return {
|
|
60
|
+
on: (event, listener) => emitter.on(event, listener),
|
|
61
|
+
removeListener: (event, listener) => emitter.removeListener(event, listener),
|
|
62
|
+
stop: () => {
|
|
63
|
+
changes2.destroy();
|
|
64
|
+
emitter.removeAllListeners();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var crud_exports = {};
|
|
30
|
+
__export(crud_exports, {
|
|
31
|
+
get: () => get,
|
|
32
|
+
getAtRev: () => getAtRev,
|
|
33
|
+
put: () => put
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(crud_exports);
|
|
36
|
+
var import_needle = __toESM(require("needle"), 1);
|
|
37
|
+
var import_crud = require("../schema/crud.cjs");
|
|
38
|
+
var import_errors = require("./errors.cjs");
|
|
39
|
+
var import_logger = require("./logger.cjs");
|
|
40
|
+
const opts = {
|
|
41
|
+
json: true,
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const _getWithOptions = import_crud.CouchGetWithOptions.implement(async (config, id, getOpts) => {
|
|
47
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
48
|
+
const rev = getOpts?.rev;
|
|
49
|
+
const path = rev ? `${id}?rev=${rev}` : id;
|
|
50
|
+
const url = `${config.couch}/${path}`;
|
|
51
|
+
logger.info(`Getting document with id: ${id}, rev ${rev || "latest"}`);
|
|
52
|
+
try {
|
|
53
|
+
const resp = await (0, import_needle.default)("get", url, opts);
|
|
54
|
+
if (!resp) {
|
|
55
|
+
logger.error("No response received from get request");
|
|
56
|
+
throw new import_errors.RetryableError("no response", 503);
|
|
57
|
+
}
|
|
58
|
+
const result = resp?.body || {};
|
|
59
|
+
if (resp.statusCode === 404) {
|
|
60
|
+
if (config.throwOnGetNotFound) {
|
|
61
|
+
logger.warn(`Document not found (throwing error): ${id}, rev ${rev || "latest"}`);
|
|
62
|
+
throw new import_errors.NotFoundError(id, result.reason || "not_found");
|
|
63
|
+
} else {
|
|
64
|
+
logger.debug(`Document not found (returning undefined): ${id}, rev ${rev || "latest"}`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (import_errors.RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
69
|
+
logger.warn(`Retryable status code received: ${resp.statusCode}`);
|
|
70
|
+
throw new import_errors.RetryableError(result.reason || "retryable error", resp.statusCode);
|
|
71
|
+
}
|
|
72
|
+
if (resp.statusCode !== 200) {
|
|
73
|
+
logger.error(`Unexpected status code: ${resp.statusCode}`);
|
|
74
|
+
throw new Error(result.reason || "failed");
|
|
75
|
+
}
|
|
76
|
+
logger.info(`Successfully retrieved document: ${id}, rev ${rev || "latest"}`);
|
|
77
|
+
return result;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
logger.error("Error during get operation:", err);
|
|
80
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const get = import_crud.CouchGet.implement(async (config, id) => {
|
|
84
|
+
const getOptions = {};
|
|
85
|
+
return _getWithOptions(config, id, getOptions);
|
|
86
|
+
});
|
|
87
|
+
const getAtRev = import_crud.CouchGetAtRev.implement(async (config, id, rev) => {
|
|
88
|
+
const getOptions = { rev };
|
|
89
|
+
return _getWithOptions(config, id, getOptions);
|
|
90
|
+
});
|
|
91
|
+
const put = import_crud.CouchPut.implement(async (config, doc) => {
|
|
92
|
+
const logger = (0, import_logger.createLogger)(config);
|
|
93
|
+
const url = `${config.couch}/${doc._id}`;
|
|
94
|
+
const body = doc;
|
|
95
|
+
logger.info(`Putting document with id: ${doc._id}`);
|
|
96
|
+
let resp;
|
|
97
|
+
try {
|
|
98
|
+
resp = await (0, import_needle.default)("put", url, body, opts);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
logger.error("Error during put operation:", err);
|
|
101
|
+
import_errors.RetryableError.handleNetworkError(err);
|
|
102
|
+
}
|
|
103
|
+
if (!resp) {
|
|
104
|
+
logger.error("No response received from put request");
|
|
105
|
+
throw new import_errors.RetryableError("no response", 503);
|
|
106
|
+
}
|
|
107
|
+
const result = resp?.body || {};
|
|
108
|
+
result.statusCode = resp.statusCode;
|
|
109
|
+
if (resp.statusCode === 409) {
|
|
110
|
+
logger.warn(`Conflict detected for document: ${doc._id}`);
|
|
111
|
+
result.ok = false;
|
|
112
|
+
result.error = "conflict";
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
if (import_errors.RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
116
|
+
logger.warn(`Retryable status code received: ${resp.statusCode}`);
|
|
117
|
+
throw new import_errors.RetryableError(result.reason || "retryable error", resp.statusCode);
|
|
118
|
+
}
|
|
119
|
+
logger.info(`Successfully saved document: ${doc._id}`);
|
|
120
|
+
return result;
|
|
121
|
+
});
|