@nxtedition/lib 19.8.13 → 19.8.14
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/couch.js +174 -145
- package/package.json +1 -1
package/couch.js
CHANGED
|
@@ -191,12 +191,6 @@ export function makeCouch(opts) {
|
|
|
191
191
|
throw new Error('invalid pathname')
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
const batched = options.batched || false
|
|
195
|
-
const live = options.live == null || !!options.live
|
|
196
|
-
const retry = options.retry
|
|
197
|
-
|
|
198
|
-
let retryCount = 0
|
|
199
|
-
|
|
200
194
|
const ac = new AbortController()
|
|
201
195
|
const onAbort = () => {
|
|
202
196
|
ac.abort()
|
|
@@ -214,169 +208,204 @@ export function makeCouch(opts) {
|
|
|
214
208
|
}
|
|
215
209
|
}
|
|
216
210
|
|
|
217
|
-
|
|
211
|
+
const batched = options.batched || false
|
|
212
|
+
const live = options.live == null || !!options.live
|
|
213
|
+
const retry = options.retry
|
|
214
|
+
const limit = options.limit
|
|
215
|
+
|
|
218
216
|
try {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
217
|
+
// Don't stay connected to the same socket to allow server to load balance.
|
|
218
|
+
const socketCount = 128 * 1024
|
|
219
|
+
let remaining = Number(limit) || Infinity
|
|
220
|
+
while (remaining > 0) {
|
|
221
|
+
let count = 0
|
|
222
|
+
for await (const changes of _changes({
|
|
223
|
+
batched,
|
|
224
|
+
live,
|
|
225
|
+
retry,
|
|
226
|
+
limit: Math.min(remaining, socketCount),
|
|
227
|
+
params,
|
|
228
|
+
method,
|
|
229
|
+
body,
|
|
230
|
+
signal: ac.signal,
|
|
231
|
+
client,
|
|
232
|
+
})) {
|
|
233
|
+
yield changes
|
|
234
|
+
count += changes.length
|
|
235
|
+
remaining -= changes.length
|
|
236
|
+
}
|
|
237
|
+
assert(remaining >= 0)
|
|
238
|
+
if (!live && count < socketCount) {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} finally {
|
|
243
|
+
ac.abort()
|
|
244
|
+
if (signal) {
|
|
245
|
+
if (signal.off) {
|
|
246
|
+
signal.off('abort', onAbort)
|
|
247
|
+
} else if (signal.removeEventListener) {
|
|
248
|
+
signal.removeEventListener('abort', onAbort)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
223
253
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
254
|
+
async function* _changes({ batched, live, retry, limit, params, method, body, signal, client }) {
|
|
255
|
+
let retryCount = 0
|
|
256
|
+
let remaining = Number(limit) || Infinity
|
|
257
|
+
while (true) {
|
|
258
|
+
let src
|
|
259
|
+
try {
|
|
260
|
+
const query = { ...params, feed: live ? 'continuous' : 'normal' }
|
|
227
261
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
idempotent: false,
|
|
232
|
-
blocking: true,
|
|
233
|
-
method,
|
|
234
|
-
body: JSON.stringify(body),
|
|
235
|
-
signal: ac.signal,
|
|
236
|
-
headers: {
|
|
237
|
-
'user-agent': userAgent,
|
|
238
|
-
'request-id': genReqId(),
|
|
239
|
-
...(body ? { 'content-type': 'application/json' } : {}),
|
|
240
|
-
},
|
|
241
|
-
bodyTimeout: 2 * (params.heartbeat || 60e3),
|
|
242
|
-
}
|
|
262
|
+
if (Number.isFinite(remaining)) {
|
|
263
|
+
query.limit = remaining
|
|
264
|
+
}
|
|
243
265
|
|
|
244
|
-
|
|
266
|
+
const ureq = {
|
|
267
|
+
path: `${dbPathname}/_changes`,
|
|
268
|
+
query,
|
|
269
|
+
idempotent: false,
|
|
270
|
+
blocking: true,
|
|
271
|
+
method,
|
|
272
|
+
body: JSON.stringify(body),
|
|
273
|
+
signal,
|
|
274
|
+
headers: {
|
|
275
|
+
'user-agent': userAgent,
|
|
276
|
+
'request-id': genReqId(),
|
|
277
|
+
...(body ? { 'content-type': 'application/json' } : {}),
|
|
278
|
+
},
|
|
279
|
+
bodyTimeout: 2 * (params.heartbeat || 60e3),
|
|
280
|
+
highWaterMark: 256 * 1024,
|
|
281
|
+
}
|
|
245
282
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
283
|
+
const ures = await client.request(ureq)
|
|
284
|
+
|
|
285
|
+
if (ures.statusCode < 200 || ures.statusCode >= 300) {
|
|
286
|
+
throw makeError(ureq, {
|
|
287
|
+
status: ures.statusCode,
|
|
288
|
+
headers: ures.headers,
|
|
289
|
+
data: await ures.body.text(),
|
|
290
|
+
})
|
|
291
|
+
}
|
|
253
292
|
|
|
254
|
-
|
|
293
|
+
src = ures.body
|
|
255
294
|
|
|
256
|
-
|
|
295
|
+
const changes = []
|
|
257
296
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
297
|
+
let resume = null
|
|
298
|
+
let error = null
|
|
299
|
+
let ended = false
|
|
300
|
+
let state = 0
|
|
262
301
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
302
|
+
function maybeResume() {
|
|
303
|
+
if (resume) {
|
|
304
|
+
resume()
|
|
305
|
+
resume = null
|
|
268
306
|
}
|
|
307
|
+
}
|
|
269
308
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
309
|
+
src
|
|
310
|
+
.on('readable', maybeResume)
|
|
311
|
+
.on('error', (err) => {
|
|
312
|
+
error = err
|
|
313
|
+
maybeResume()
|
|
314
|
+
})
|
|
315
|
+
.on('end', () => {
|
|
316
|
+
ended = true
|
|
317
|
+
maybeResume()
|
|
318
|
+
})
|
|
319
|
+
.on('close', maybeResume)
|
|
320
|
+
|
|
321
|
+
let str = ''
|
|
322
|
+
while (true) {
|
|
323
|
+
const chunk = src.read()
|
|
324
|
+
|
|
325
|
+
if (chunk !== null) {
|
|
326
|
+
const lines = (str + chunk).split(/\r?\n/)
|
|
327
|
+
str = lines.pop() ?? ''
|
|
328
|
+
|
|
329
|
+
for (const line of lines) {
|
|
330
|
+
if (line === '') {
|
|
331
|
+
// hearbeat
|
|
332
|
+
yield batched ? [] : null
|
|
333
|
+
} else if (line === ',') {
|
|
334
|
+
// Do nothing. Couch sometimes insert new line between
|
|
335
|
+
// json body and comma.
|
|
336
|
+
} else if (live) {
|
|
337
|
+
const data = JSON.parse(line)
|
|
338
|
+
if (data.last_seq) {
|
|
339
|
+
params.since = data.last_seq
|
|
340
|
+
} else {
|
|
341
|
+
params.since = data.seq || params.since
|
|
342
|
+
changes.push(data)
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
// NOTE: This makes some assumptions about the format of the JSON.
|
|
346
|
+
if (state === 0) {
|
|
347
|
+
if (line.endsWith('[')) {
|
|
348
|
+
state = 1
|
|
301
349
|
} else {
|
|
302
|
-
|
|
303
|
-
changes.push(data)
|
|
350
|
+
assert(false, 'invalid head: ' + line)
|
|
304
351
|
}
|
|
305
|
-
} else {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
assert(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const idx = line.lastIndexOf('}') + 1
|
|
318
|
-
try {
|
|
319
|
-
assert(idx >= 0, 'invalid row: ' + idx + ' ' + line)
|
|
320
|
-
const change = JSON.parse(line.slice(0, idx))
|
|
321
|
-
params.since = change.seq || params.since
|
|
322
|
-
changes.push(change)
|
|
323
|
-
} catch (err) {
|
|
324
|
-
throw Object.assign(err, { data: line })
|
|
325
|
-
}
|
|
352
|
+
} else if (state === 1) {
|
|
353
|
+
if (line.startsWith(']')) {
|
|
354
|
+
state = 2
|
|
355
|
+
} else {
|
|
356
|
+
const idx = line.lastIndexOf('}') + 1
|
|
357
|
+
try {
|
|
358
|
+
assert(idx >= 0, 'invalid row: ' + idx + ' ' + line)
|
|
359
|
+
const change = JSON.parse(line.slice(0, idx))
|
|
360
|
+
params.since = change.seq || params.since
|
|
361
|
+
changes.push(change)
|
|
362
|
+
} catch (err) {
|
|
363
|
+
throw Object.assign(err, { data: line })
|
|
326
364
|
}
|
|
327
|
-
} else if (state === 2) {
|
|
328
|
-
state = 3
|
|
329
|
-
params.since = JSON.parse('{' + line).last_seq
|
|
330
|
-
assert(params.since, 'invalid trailer: ' + line)
|
|
331
365
|
}
|
|
366
|
+
} else if (state === 2) {
|
|
367
|
+
state = 3
|
|
368
|
+
params.since = JSON.parse('{' + line).last_seq
|
|
369
|
+
assert(params.since, 'invalid trailer: ' + line)
|
|
332
370
|
}
|
|
333
371
|
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
372
|
+
}
|
|
373
|
+
} else if (changes.length) {
|
|
374
|
+
remaining -= changes.length
|
|
375
|
+
assert(remaining >= 0, 'invalid remaining: ' + remaining)
|
|
337
376
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
} else {
|
|
341
|
-
yield* changes.splice(0)
|
|
342
|
-
}
|
|
343
|
-
} else if (error) {
|
|
344
|
-
throw error
|
|
345
|
-
} else if (!ended) {
|
|
346
|
-
await new Promise((resolve) => {
|
|
347
|
-
resume = resolve
|
|
348
|
-
})
|
|
377
|
+
if (batched) {
|
|
378
|
+
yield changes.splice(0)
|
|
349
379
|
} else {
|
|
350
|
-
|
|
380
|
+
yield* changes.splice(0)
|
|
351
381
|
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
Object.assign(
|
|
359
|
-
retryState,
|
|
360
|
-
await retry(err, retryCount++, retryState, { signal }, () =>
|
|
361
|
-
delay(err, retryCount, { signal }),
|
|
362
|
-
),
|
|
363
|
-
)
|
|
364
|
-
params.since = retryState.since ?? 0
|
|
382
|
+
} else if (error) {
|
|
383
|
+
throw error
|
|
384
|
+
} else if (!ended) {
|
|
385
|
+
await new Promise((resolve) => {
|
|
386
|
+
resume = resolve
|
|
387
|
+
})
|
|
365
388
|
} else {
|
|
366
|
-
|
|
389
|
+
return
|
|
367
390
|
}
|
|
368
|
-
} finally {
|
|
369
|
-
src?.on('error', () => {}).destroy()
|
|
370
391
|
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
392
|
+
} catch (err) {
|
|
393
|
+
if (err.name === 'AbortError') {
|
|
394
|
+
throw err
|
|
395
|
+
} else if (typeof retry === 'function') {
|
|
396
|
+
const retryState = { since: params.since }
|
|
397
|
+
Object.assign(
|
|
398
|
+
retryState,
|
|
399
|
+
await retry(err, retryCount++, retryState, { signal }, () =>
|
|
400
|
+
delay(err, retryCount, { signal }),
|
|
401
|
+
),
|
|
402
|
+
)
|
|
403
|
+
params.since = retryState.since ?? 0
|
|
404
|
+
} else {
|
|
405
|
+
await delay(err, retryCount, { signal })
|
|
379
406
|
}
|
|
407
|
+
} finally {
|
|
408
|
+
src?.on('error', () => {}).destroy()
|
|
380
409
|
}
|
|
381
410
|
}
|
|
382
411
|
}
|