hide-a-bed 5.0.0 → 5.0.2
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/impl/changes.mjs +2 -7
- package/impl/errors.mjs +1 -1
- package/impl/query.mjs +14 -6
- package/impl/stream.mjs +2 -2
- package/impl/sugar/lock.mjs +3 -3
- package/impl/sugar/watch.mjs +66 -66
- package/log.txt +84 -0
- package/package.json +1 -1
- package/schema/changes.mjs +1 -1
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
|
+
|
package/impl/changes.mjs
CHANGED
|
@@ -12,15 +12,10 @@ import { Changes } from '../schema/changes.mjs'
|
|
|
12
12
|
* }} ChangeInfo */
|
|
13
13
|
// @ts-ignore
|
|
14
14
|
import ChangesStream from 'changes-stream'
|
|
15
|
-
import { createLogger } from './logger.mjs'
|
|
16
|
-
import { sleep } from './patch.mjs'
|
|
17
|
-
|
|
18
|
-
const MAX_RETRY_DELAY = 30000 // 30 seconds
|
|
19
15
|
|
|
20
16
|
/** @type { import('../schema/changes.mjs').ChangesSchema } */
|
|
21
17
|
export const changes = Changes.implement(async (config, onChange, options = {}) => {
|
|
22
18
|
const emitter = new EventEmitter()
|
|
23
|
-
const logger = createLogger(config)
|
|
24
19
|
options.db = config.couch
|
|
25
20
|
if (options.since && options.since === 'now') {
|
|
26
21
|
const opts = {
|
|
@@ -37,12 +32,12 @@ export const changes = Changes.implement(async (config, onChange, options = {})
|
|
|
37
32
|
const changes = ChangesStream(options)
|
|
38
33
|
|
|
39
34
|
changes.on('readable', () => {
|
|
40
|
-
const change = changes.read()
|
|
35
|
+
const change = changes.read()
|
|
41
36
|
if (change.results && Array.isArray(change.results)) {
|
|
42
37
|
// emit each one seperate
|
|
43
38
|
change.results.forEach((/** @type {ChangeInfo} */ c) => emitter.emit('change', c))
|
|
44
39
|
} else emitter.emit('change', change)
|
|
45
|
-
})
|
|
40
|
+
})
|
|
46
41
|
|
|
47
42
|
// Bind the provided change listener
|
|
48
43
|
emitter.on('change', onChange)
|
package/impl/errors.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export class NotFoundError extends Error {
|
|
|
11
11
|
* @param {string} docId - The ID of the document that wasn't found
|
|
12
12
|
* @param {string} [message] - Optional error message
|
|
13
13
|
*/
|
|
14
|
-
constructor(docId, message = 'Document not found') {
|
|
14
|
+
constructor (docId, message = 'Document not found') {
|
|
15
15
|
super(message)
|
|
16
16
|
this.name = 'NotFoundError'
|
|
17
17
|
this.docId = docId
|
package/impl/query.mjs
CHANGED
|
@@ -22,7 +22,6 @@ export const query = SimpleViewQuery.implement(async (config, view, options = {}
|
|
|
22
22
|
|
|
23
23
|
// @ts-ignore
|
|
24
24
|
let qs = queryString(options, ['key', 'startkey', 'endkey', 'reduce', 'group', 'group_level', 'stale', 'limit'])
|
|
25
|
-
logger.debug('Generated query string:', qs)
|
|
26
25
|
let method = 'GET'
|
|
27
26
|
let payload = null
|
|
28
27
|
const opts = {
|
|
@@ -35,21 +34,30 @@ export const query = SimpleViewQuery.implement(async (config, view, options = {}
|
|
|
35
34
|
// If keys are supplied, issue a POST to circumvent GET query string limits
|
|
36
35
|
// see http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options
|
|
37
36
|
if (typeof options.keys !== 'undefined') {
|
|
38
|
-
const MAX_URL_LENGTH = 2000
|
|
37
|
+
const MAX_URL_LENGTH = 2000
|
|
39
38
|
// according to http://stackoverflow.com/a/417184/680742,
|
|
40
39
|
// the de facto URL length limit is 2000 characters
|
|
41
40
|
|
|
42
|
-
const
|
|
41
|
+
const _options = structuredClone(options)
|
|
42
|
+
delete _options.keys
|
|
43
|
+
qs = queryString(_options, ['key', 'startkey', 'endkey', 'reduce', 'group', 'group_level', 'stale', 'limit'])
|
|
44
|
+
|
|
45
|
+
const keysAsString = `keys=${JSON.stringify(options.keys)}`
|
|
46
|
+
|
|
43
47
|
if (keysAsString.length + qs.length + 1 <= MAX_URL_LENGTH) {
|
|
44
48
|
// If the keys are short enough, do a GET. we do this to work around
|
|
45
49
|
// Safari not understanding 304s on POSTs (see pouchdb/pouchdb#1239)
|
|
46
|
-
|
|
50
|
+
method = 'GET'
|
|
51
|
+
if (qs.length > 0) qs += '&'
|
|
52
|
+
else qs = '?'
|
|
53
|
+
qs += keysAsString
|
|
47
54
|
} else {
|
|
48
|
-
method = 'POST'
|
|
49
|
-
payload = {keys: options.keys}
|
|
55
|
+
method = 'POST'
|
|
56
|
+
payload = { keys: options.keys }
|
|
50
57
|
}
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
logger.debug('Generated query string:', qs)
|
|
53
61
|
const url = `${config.couch}/${view}?${qs.toString()}`
|
|
54
62
|
// @ts-ignore
|
|
55
63
|
let results
|
package/impl/stream.mjs
CHANGED
|
@@ -25,7 +25,7 @@ export const queryStream = (rawConfig, view, options, onRow) => new Promise((res
|
|
|
25
25
|
if (typeof options.keys !== 'undefined') {
|
|
26
26
|
const MAX_URL_LENGTH = 2000
|
|
27
27
|
const keysAsString = `keys=${encodeURIComponent(JSON.stringify(options.keys))}`
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
if (keysAsString.length + qs.length + 1 <= MAX_URL_LENGTH) {
|
|
30
30
|
// If the keys are short enough, do a GET
|
|
31
31
|
qs += (qs[0] === '?' ? '&' : '?') + keysAsString
|
|
@@ -70,7 +70,7 @@ export const queryStream = (rawConfig, view, options, onRow) => new Promise((res
|
|
|
70
70
|
resolve(undefined) // all work should be done in the stream
|
|
71
71
|
})
|
|
72
72
|
|
|
73
|
-
const req = method === 'GET'
|
|
73
|
+
const req = method === 'GET'
|
|
74
74
|
? needle.get(url, opts)
|
|
75
75
|
: needle.post(url, payload, opts)
|
|
76
76
|
|
package/impl/sugar/lock.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { createLogger } from '../logger.mjs'
|
|
|
5
5
|
/** @type {import('../../schema/sugar/lock.mjs').CreateLockSchema} */
|
|
6
6
|
export const createLock = CreateLock.implement(async (config, docId, options) => {
|
|
7
7
|
const logger = createLogger(config)
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
if (!options.enableLocking) {
|
|
10
10
|
logger.debug('Locking disabled, returning true')
|
|
11
11
|
return true
|
|
@@ -37,7 +37,7 @@ export const createLock = CreateLock.implement(async (config, docId, options) =>
|
|
|
37
37
|
/** @type {import('../../schema/sugar/lock.mjs').RemoveLockSchema} */
|
|
38
38
|
export const removeLock = RemoveLock.implement(async (config, docId, options) => {
|
|
39
39
|
const logger = createLogger(config)
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
if (!options.enableLocking) {
|
|
42
42
|
logger.debug('Locking disabled, skipping unlock')
|
|
43
43
|
return
|
|
@@ -50,7 +50,7 @@ export const removeLock = RemoveLock.implement(async (config, docId, options) =>
|
|
|
50
50
|
|
|
51
51
|
const _id = `lock-${docId}`
|
|
52
52
|
const existingLock = await get(config, _id)
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
if (!existingLock) {
|
|
55
55
|
logger.debug(`No lock found for ${docId}`)
|
|
56
56
|
return
|
package/impl/sugar/watch.mjs
CHANGED
|
@@ -28,7 +28,7 @@ export const watchDocs = WatchDocs.implement((config, docIds, onChange, options
|
|
|
28
28
|
const includeDocs = options.include_docs ?? false
|
|
29
29
|
const ids = _docIds.join('","')
|
|
30
30
|
const url = `${config.couch}/_changes?feed=${feed}&since=${lastSeq}&include_docs=${includeDocs}&filter=_doc_ids&doc_ids=["${ids}"]`
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
const opts = {
|
|
33
33
|
headers: { 'Content-Type': 'application/json' },
|
|
34
34
|
parse_response: false
|
|
@@ -37,79 +37,79 @@ export const watchDocs = WatchDocs.implement((config, docIds, onChange, options
|
|
|
37
37
|
let buffer = ''
|
|
38
38
|
currentRequest = needle.get(url, opts)
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Keep the last partial line in the buffer
|
|
45
|
-
buffer = lines.pop() || ''
|
|
40
|
+
currentRequest.on('data', chunk => {
|
|
41
|
+
buffer += chunk.toString()
|
|
42
|
+
const lines = buffer.split('\n')
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
// Keep the last partial line in the buffer
|
|
45
|
+
buffer = lines.pop() || ''
|
|
46
|
+
|
|
47
|
+
// Process complete lines
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (line.trim()) {
|
|
50
|
+
try {
|
|
51
|
+
const change = JSON.parse(line)
|
|
52
|
+
if (!change.id) return null // ignore just last_seq
|
|
53
|
+
logger.debug(`Change detected, watching [${_docIds}]`, change)
|
|
54
|
+
lastSeq = change.seq || change.last_seq
|
|
55
|
+
emitter.emit('change', change)
|
|
56
|
+
} catch (err) {
|
|
57
|
+
logger.error('Error parsing change:', err, 'Line:', line)
|
|
58
|
+
}
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
}
|
|
61
|
-
})
|
|
61
|
+
})
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
handleReconnect()
|
|
69
|
-
} else {
|
|
70
|
-
// Reset retry count on successful connection
|
|
71
|
-
retryCount = 0
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
currentRequest.on('error', async err => {
|
|
76
|
-
if (stopping) {
|
|
77
|
-
logger.info('stopping in progress, ignore stream error')
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
logger.error(`Network error during stream, watching [${_docIds}]:`, err.toString())
|
|
81
|
-
try {
|
|
82
|
-
RetryableError.handleNetworkError(err)
|
|
83
|
-
} catch (filteredError) {
|
|
84
|
-
if (filteredError instanceof RetryableError) {
|
|
85
|
-
logger.info(`Retryable error, watching [${_docIds}]:`, filteredError.toString())
|
|
63
|
+
currentRequest.on('response', response => {
|
|
64
|
+
logger.debug(`Received response with status code, watching [${_docIds}]: ${response.statusCode}`)
|
|
65
|
+
if (RetryableError.isRetryableStatusCode(response.statusCode)) {
|
|
66
|
+
logger.warn(`Retryable status code received: ${response.statusCode}`)
|
|
67
|
+
currentRequest.abort()
|
|
86
68
|
handleReconnect()
|
|
87
69
|
} else {
|
|
88
|
-
|
|
89
|
-
|
|
70
|
+
// Reset retry count on successful connection
|
|
71
|
+
retryCount = 0
|
|
90
72
|
}
|
|
91
|
-
}
|
|
92
|
-
})
|
|
73
|
+
})
|
|
93
74
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
75
|
+
currentRequest.on('error', async err => {
|
|
76
|
+
if (stopping) {
|
|
77
|
+
logger.info('stopping in progress, ignore stream error')
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
logger.error(`Network error during stream, watching [${_docIds}]:`, err.toString())
|
|
97
81
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
82
|
+
RetryableError.handleNetworkError(err)
|
|
83
|
+
} catch (filteredError) {
|
|
84
|
+
if (filteredError instanceof RetryableError) {
|
|
85
|
+
logger.info(`Retryable error, watching [${_docIds}]:`, filteredError.toString())
|
|
86
|
+
handleReconnect()
|
|
87
|
+
} else {
|
|
88
|
+
logger.error(`Non-retryable error, watching [${_docIds}]`, filteredError.toString())
|
|
89
|
+
emitter.emit('error', filteredError)
|
|
90
|
+
}
|
|
103
91
|
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
currentRequest.on('end', () => {
|
|
95
|
+
// Process any remaining data in buffer
|
|
96
|
+
if (buffer.trim()) {
|
|
97
|
+
try {
|
|
98
|
+
const change = JSON.parse(buffer)
|
|
99
|
+
logger.debug('Final change detected:', change)
|
|
100
|
+
emitter.emit('change', change)
|
|
101
|
+
} catch (err) {
|
|
102
|
+
logger.error('Error parsing final change:', err)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
logger.info('Stream completed. Last seen seq: ', lastSeq)
|
|
106
|
+
emitter.emit('end', { lastSeq })
|
|
107
|
+
|
|
108
|
+
// If the stream ends and we're not stopping, attempt to reconnect
|
|
109
|
+
if (!stopping) {
|
|
110
|
+
handleReconnect()
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
const handleReconnect = async () => {
|
|
@@ -123,10 +123,10 @@ export const watchDocs = WatchDocs.implement((config, docIds, onChange, options
|
|
|
123
123
|
|
|
124
124
|
const delay = Math.min(initialDelay * Math.pow(2, retryCount), maxDelay)
|
|
125
125
|
retryCount++
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
logger.info(`Attempting to reconnect in ${delay}ms (attempt ${retryCount} of ${maxRetries})`)
|
|
128
128
|
await sleep(delay)
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
try {
|
|
131
131
|
connect()
|
|
132
132
|
} catch (err) {
|
package/log.txt
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[Tue, 25 Feb 2025 02:45:58 GMT] [info] [<0.000.0>] pouchdb-server has started on http://127.0.0.1:8985/
|
|
2
|
+
[Tue, 25 Feb 2025 02:45:58 GMT] [info] [<0.000.0>] database is in-memory; no changes will be saved.
|
|
3
|
+
[Tue, 25 Feb 2025 02:45:58 GMT] [info] [<0.000.0>] navigate to http://127.0.0.1:8985/_utils for the Fauxton UI.
|
|
4
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb 201
|
|
5
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/testdoc 201
|
|
6
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/testdoc 200
|
|
7
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/testdoc-not-there 404
|
|
8
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/testdoc-not-there 404
|
|
9
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/notThereDoc 409
|
|
10
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
11
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda 201
|
|
12
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
13
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_bulk_docs 201
|
|
14
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda 201
|
|
15
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-1 201
|
|
16
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
17
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-2 201
|
|
18
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
19
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_bulk_docs 201
|
|
20
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-2 201
|
|
21
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-3 201
|
|
22
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
23
|
+
[Tue, 25 Feb 2025 02:45:59 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/a 201
|
|
24
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_bulk_docs 201
|
|
25
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_bulk_docs 201
|
|
26
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-3 201
|
|
27
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
28
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/conflict-test 201
|
|
29
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:conflict-error 201
|
|
30
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
31
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:bulk-error 201
|
|
32
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
33
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/lock-doc-to-lock 201
|
|
34
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 200
|
|
35
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/lock-doc-to-lock 409
|
|
36
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 200
|
|
37
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/lock-doc-to-lock 201
|
|
38
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 404
|
|
39
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/lock-doc-to-lock 201
|
|
40
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 200
|
|
41
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 200
|
|
42
|
+
[Tue, 25 Feb 2025 02:46:00 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock-2 404
|
|
43
|
+
[Tue, 25 Feb 2025 02:48:53 GMT] [info] [<0.000.0>] pouchdb-server has started on http://127.0.0.1:8985/
|
|
44
|
+
[Tue, 25 Feb 2025 02:48:53 GMT] [info] [<0.000.0>] database is in-memory; no changes will be saved.
|
|
45
|
+
[Tue, 25 Feb 2025 02:48:53 GMT] [info] [<0.000.0>] navigate to http://127.0.0.1:8985/_utils for the Fauxton UI.
|
|
46
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb 201
|
|
47
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/testdoc 201
|
|
48
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/testdoc 200
|
|
49
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/testdoc-not-there 404
|
|
50
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/testdoc-not-there 404
|
|
51
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/notThereDoc 409
|
|
52
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
53
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda 201
|
|
54
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
55
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_bulk_docs 201
|
|
56
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda 201
|
|
57
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-1 201
|
|
58
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
59
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-2 201
|
|
60
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
61
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_bulk_docs 201
|
|
62
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-2 201
|
|
63
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-3 201
|
|
64
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
65
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/a 201
|
|
66
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_bulk_docs 201
|
|
67
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_bulk_docs 201
|
|
68
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:fsda-3 201
|
|
69
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
70
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/conflict-test 201
|
|
71
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:conflict-error 201
|
|
72
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
73
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/txn:bulk-error 201
|
|
74
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - POST /testdb/_all_docs?include_docs=true 200
|
|
75
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/lock-doc-to-lock 201
|
|
76
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 200
|
|
77
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/lock-doc-to-lock 409
|
|
78
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 200
|
|
79
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/lock-doc-to-lock 201
|
|
80
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 404
|
|
81
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - PUT /testdb/lock-doc-to-lock 201
|
|
82
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 200
|
|
83
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock 200
|
|
84
|
+
[Tue, 25 Feb 2025 02:48:54 GMT] [info] [<0.000.0>] 127.0.0.1 - - GET /testdb/lock-doc-to-lock-2 404
|
package/package.json
CHANGED
package/schema/changes.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { CouchDoc } from './crud.mjs'
|
|
|
4
4
|
|
|
5
5
|
export const ChangesOptions = z.object({
|
|
6
6
|
feed: z.enum(['continuous', 'longpoll']).default('continuous'),
|
|
7
|
-
filter: z.any(), //z.union([z.string(), z.array()]).optional(),
|
|
7
|
+
filter: z.any(), // z.union([z.string(), z.array()]).optional(),
|
|
8
8
|
inactivity_ms: z.number().default(60 * 60 * 1000),
|
|
9
9
|
/** @type {number} */
|
|
10
10
|
timeout: z.number().optional(),
|