@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.
Files changed (2) hide show
  1. package/couch.js +174 -145
  2. 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
- let remaining = Number(options.limit) || Infinity
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
- while (true) {
220
- let src
221
- try {
222
- const query = { ...params, feed: live ? 'continuous' : 'normal' }
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
- if (Number.isFinite(remaining)) {
225
- query.limit = remaining
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
- const ureq = {
229
- path: `${dbPathname}/_changes`,
230
- query,
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
- const ures = await client.request(ureq)
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
- if (ures.statusCode < 200 || ures.statusCode >= 300) {
247
- throw makeError(ureq, {
248
- status: ures.statusCode,
249
- headers: ures.headers,
250
- data: await ures.body.text(),
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
- src = ures.body
293
+ src = ures.body
255
294
 
256
- const changes = []
295
+ const changes = []
257
296
 
258
- let resume = null
259
- let error = null
260
- let ended = false
261
- let state = 0
297
+ let resume = null
298
+ let error = null
299
+ let ended = false
300
+ let state = 0
262
301
 
263
- function maybeResume() {
264
- if (resume) {
265
- resume()
266
- resume = null
267
- }
302
+ function maybeResume() {
303
+ if (resume) {
304
+ resume()
305
+ resume = null
268
306
  }
307
+ }
269
308
 
270
- src
271
- .on('readable', maybeResume)
272
- .on('error', (err) => {
273
- error = err
274
- maybeResume()
275
- })
276
- .on('end', () => {
277
- ended = true
278
- maybeResume()
279
- })
280
- .on('close', maybeResume)
281
-
282
- let str = ''
283
- while (true) {
284
- const chunk = src.read()
285
-
286
- if (chunk !== null) {
287
- const lines = (str + chunk).split(/\r?\n/)
288
- str = lines.pop() ?? ''
289
-
290
- for (const line of lines) {
291
- if (line === '') {
292
- // hearbeat
293
- yield batched ? [] : null
294
- } else if (line === ',') {
295
- // Do nothing. Couch sometimes insert new line between
296
- // json body and comma.
297
- } else if (live) {
298
- const data = JSON.parse(line)
299
- if (data.last_seq) {
300
- params.since = data.last_seq
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
- params.since = data.seq || params.since
303
- changes.push(data)
350
+ assert(false, 'invalid head: ' + line)
304
351
  }
305
- } else {
306
- // NOTE: This makes some assumptions about the format of the JSON.
307
- if (state === 0) {
308
- if (line.endsWith('[')) {
309
- state = 1
310
- } else {
311
- assert(false, 'invalid head: ' + line)
312
- }
313
- } else if (state === 1) {
314
- if (line.startsWith(']')) {
315
- state = 2
316
- } else {
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
- } else if (changes.length) {
335
- remaining -= changes.length
336
- assert(remaining >= 0, 'invalid remaining: ' + remaining)
372
+ }
373
+ } else if (changes.length) {
374
+ remaining -= changes.length
375
+ assert(remaining >= 0, 'invalid remaining: ' + remaining)
337
376
 
338
- if (batched) {
339
- yield changes.splice(0)
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
- return
380
+ yield* changes.splice(0)
351
381
  }
352
- }
353
- } catch (err) {
354
- if (err.name === 'AbortError') {
355
- throw err
356
- } else if (typeof retry === 'function') {
357
- const retryState = { since: params.since }
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
- await delay(err, retryCount, { signal })
389
+ return
367
390
  }
368
- } finally {
369
- src?.on('error', () => {}).destroy()
370
391
  }
371
- }
372
- } finally {
373
- ac.abort()
374
- if (signal) {
375
- if (signal.off) {
376
- signal.off('abort', onAbort)
377
- } else if (signal.removeEventListener) {
378
- signal.removeEventListener('abort', onAbort)
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "19.8.13",
3
+ "version": "19.8.14",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",