@nxtedition/lib 18.0.10 → 19.0.1

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 +71 -108
  2. package/package.json +1 -1
package/couch.js CHANGED
@@ -1,3 +1,4 @@
1
+ import assert from 'node:assert'
1
2
  import createError from 'http-errors'
2
3
  import { makeWeakCache } from './weakCache.js'
3
4
  import tp from 'timers/promises'
@@ -225,16 +226,21 @@ export function makeCouch(opts) {
225
226
  }
226
227
  }
227
228
 
228
- async function* continuous() {
229
+ async function* parse(live) {
230
+ let remaining = Number(options.limit) || Infinity
231
+
232
+ const params2 = {
233
+ ...params,
234
+ ...options.query,
235
+ feed: live ? 'continuous' : 'normal',
236
+ }
237
+
238
+ if (Number.isFinite(remaining)) {
239
+ params.limit = remaining
240
+ }
241
+
229
242
  const req = {
230
- path:
231
- dbPathname +
232
- '/_changes' +
233
- `?${new URLSearchParams({
234
- ...params,
235
- ...options.query,
236
- feed: 'continuous',
237
- })}`,
243
+ path: `${dbPathname}/_changes?${new URLSearchParams(params2)}`,
238
244
  idempotent: false,
239
245
  blocking: true,
240
246
  method,
@@ -245,10 +251,14 @@ export function makeCouch(opts) {
245
251
  'request-id': genReqId(),
246
252
  ...(body ? { 'content-type': 'application/json' } : {}),
247
253
  },
254
+ throwOnError: true,
248
255
  highWaterMark: 256 * 1024, // TODO (fix): Needs support in undici...
249
256
  bodyTimeout: 2 * (params.heartbeat || 60e3),
250
257
  }
251
258
 
259
+ const HEAD = '{"results":['
260
+ const TAIL = '],'
261
+
252
262
  try {
253
263
  const res = await client.request(req)
254
264
 
@@ -262,29 +272,68 @@ export function makeCouch(opts) {
262
272
 
263
273
  retryCount = 0
264
274
 
275
+ const decoder = new TextDecoder()
276
+
265
277
  let str = ''
278
+ let state = 0
266
279
  for await (const chunk of res.body) {
267
- const lines = (str + chunk).split('\n')
280
+ str += decoder.decode(chunk, { stream: true })
281
+
282
+ const lines = str.split('\n')
268
283
  str = lines.pop() ?? ''
269
284
 
270
- const results = batched ? [] : null
285
+ const changes = []
271
286
  for (const line of lines) {
272
- if (line) {
273
- const change = JSON.parse(line)
274
- if (change.seq) {
275
- params.since = change.seq
287
+ if (line === '') {
288
+ continue
289
+ }
290
+ if (live) {
291
+ const data = JSON.parse(line)
292
+ if (data.last_seq) {
293
+ params.since = data.last_seq
294
+ assert(params.since, 'invalid last_seq: ' + params.since)
295
+ } else {
296
+ params.since = data.seq || params.since
297
+ changes.push(data)
276
298
  }
277
- if (results) {
278
- results.push(change)
299
+ } else {
300
+ // NOTE: This makes some assumptions about the format of the JSON.
301
+ if (state === 0) {
302
+ if (line === HEAD) {
303
+ state = 1
304
+ } else {
305
+ assert(line.length < HEAD.length, 'invalid line: ' + line)
306
+ }
307
+ } else if (state === 1) {
308
+ if (line === TAIL) {
309
+ state = 2
310
+ } else {
311
+ const idx = line.lastIndexOf('}') + 1
312
+ assert(idx > 0, 'invalid line; ' + line)
313
+ const data = JSON.parse(line.slice(0, idx))
314
+ params.since = data.seq || params.since
315
+ changes.push(data)
316
+ }
317
+ } else if (state === 2) {
318
+ state = 3
319
+ params.since = JSON.parse('{' + line).last_seq
320
+ assert(params.since, 'invalid last_seq: ' + params.since)
279
321
  } else {
280
- yield change
322
+ assert(false, 'invalid state: ' + state)
281
323
  }
282
324
  }
283
325
  }
284
326
 
285
- if (results?.length) {
286
- yield results
327
+ if (changes.length > 0) {
328
+ if (batched) {
329
+ yield changes
330
+ } else {
331
+ yield* changes
332
+ }
287
333
  }
334
+
335
+ remaining -= changes.length
336
+ assert(remaining >= 0, 'invalid remaining: ' + remaining)
288
337
  }
289
338
  } catch (err) {
290
339
  Object.assign(err, { data: req })
@@ -292,99 +341,13 @@ export function makeCouch(opts) {
292
341
  }
293
342
  }
294
343
 
295
- async function* normal() {
296
- const batchSize =
297
- options.batch_size ?? options.batchSize ?? (params.include_docs ? 512 : 4096)
298
- let remaining = parseInt(options.limit) || Infinity
299
-
300
- const next = async () => {
301
- const req = {
302
- path:
303
- dbPathname +
304
- '/_changes' +
305
- `?${new URLSearchParams({
306
- ...params,
307
- ...options.query,
308
- limit: Math.min(remaining, batchSize),
309
- feed: live ? 'longpoll' : 'normal',
310
- })}`,
311
- idempotent: true,
312
- blocking: live,
313
- method,
314
- maxRedirects: 4,
315
- body: JSON.stringify(body),
316
- signal: ac.signal,
317
- headers: {
318
- 'user-agent': userAgent,
319
- 'request-id': genReqId(),
320
- ...(body ? { 'content-type': 'application/json' } : {}),
321
- },
322
- }
323
-
324
- try {
325
- const res = await client.request(req)
326
-
327
- if (res.statusCode < 200 || res.statusCode >= 300) {
328
- throw makeError(req, {
329
- status: res.statusCode,
330
- headers: res.headers,
331
- data: await res.body.text(),
332
- })
333
- }
334
-
335
- return await res.body.json()
336
- } catch (err) {
337
- Object.assign(err, { data: req })
338
- throw err
339
- }
340
- }
341
-
342
- let promise
343
- while (remaining) {
344
- const { last_seq: seq, results, err } = await (promise ?? next())
345
- promise = null
346
-
347
- if (err) {
348
- throw err
349
- }
350
-
351
- retryCount = 0
352
-
353
- if (seq) {
354
- params.since = seq
355
- if (results.length > 0 && !results.at(-1)?.seq) {
356
- results.at(-1).seq = seq
357
- }
358
- }
359
-
360
- remaining -= results.length
361
-
362
- if (!live && results.length === 0) {
363
- return
364
- }
365
-
366
- promise = next().catch((err) => ({ err }))
367
-
368
- if (batched) {
369
- yield results
370
- } else {
371
- yield* results
372
- }
373
- }
374
- }
375
-
376
344
  try {
377
345
  while (true) {
378
346
  try {
379
- if (live && !options.batchSize && !options.batch_size) {
380
- yield* continuous()
381
- } else {
382
- yield* normal()
383
- }
347
+ yield* parse(live)
384
348
  return
385
349
  } catch (err) {
386
- err.reason ??= err.body?.reason
387
- if (err.name === 'AbortError' || client.destroyed) {
350
+ if (err.name === 'AbortError') {
388
351
  throw err
389
352
  } else if (typeof retry === 'function') {
390
353
  const retryState = { since: params.since }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "18.0.10",
3
+ "version": "19.0.1",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",