@electric-sql/client 1.0.2 → 1.0.4
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/dist/cjs/index.cjs +172 -123
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.legacy-esm.js +170 -123
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +172 -123
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +238 -172
- package/src/constants.ts +1 -0
- package/src/fetch.ts +6 -1
package/src/client.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
TABLE_QUERY_PARAM,
|
|
40
40
|
REPLICA_PARAM,
|
|
41
41
|
FORCE_DISCONNECT_AND_REFRESH,
|
|
42
|
+
PAUSE_STREAM,
|
|
42
43
|
} from './constants'
|
|
43
44
|
|
|
44
45
|
const RESERVED_PARAMS: Set<ReservedParamKeys> = new Set([
|
|
@@ -334,6 +335,7 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
334
335
|
>()
|
|
335
336
|
|
|
336
337
|
#started = false
|
|
338
|
+
#state = `active` as `active` | `pause-requested` | `paused`
|
|
337
339
|
#lastOffset: Offset
|
|
338
340
|
#liveCacheBuster: string // Seconds since our Electric Epoch 😎
|
|
339
341
|
#lastSyncedAt?: number // unix time
|
|
@@ -374,6 +376,8 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
374
376
|
createFetchWithChunkBuffer(fetchWithBackoffClient)
|
|
375
377
|
)
|
|
376
378
|
)
|
|
379
|
+
|
|
380
|
+
this.#subscribeToVisibilityChanges()
|
|
377
381
|
}
|
|
378
382
|
|
|
379
383
|
get shapeHandle() {
|
|
@@ -392,181 +396,11 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
392
396
|
return this.#lastOffset
|
|
393
397
|
}
|
|
394
398
|
|
|
395
|
-
async #start() {
|
|
396
|
-
if (this.#started) throw new Error(`Cannot start stream twice`)
|
|
399
|
+
async #start(): Promise<void> {
|
|
397
400
|
this.#started = true
|
|
398
401
|
|
|
399
402
|
try {
|
|
400
|
-
|
|
401
|
-
(!this.options.signal?.aborted && !this.#isUpToDate) ||
|
|
402
|
-
this.options.subscribe
|
|
403
|
-
) {
|
|
404
|
-
const { url, signal } = this.options
|
|
405
|
-
|
|
406
|
-
// Resolve headers and params in parallel
|
|
407
|
-
const [requestHeaders, params] = await Promise.all([
|
|
408
|
-
resolveHeaders(this.options.headers),
|
|
409
|
-
this.options.params
|
|
410
|
-
? toInternalParams(convertWhereParamsToObj(this.options.params))
|
|
411
|
-
: undefined,
|
|
412
|
-
])
|
|
413
|
-
|
|
414
|
-
// Validate params after resolution
|
|
415
|
-
if (params) {
|
|
416
|
-
validateParams(params)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const fetchUrl = new URL(url)
|
|
420
|
-
|
|
421
|
-
// Add PostgreSQL-specific parameters
|
|
422
|
-
if (params) {
|
|
423
|
-
if (params.table)
|
|
424
|
-
setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table)
|
|
425
|
-
if (params.where)
|
|
426
|
-
setQueryParam(fetchUrl, WHERE_QUERY_PARAM, params.where)
|
|
427
|
-
if (params.columns)
|
|
428
|
-
setQueryParam(fetchUrl, COLUMNS_QUERY_PARAM, params.columns)
|
|
429
|
-
if (params.replica)
|
|
430
|
-
setQueryParam(fetchUrl, REPLICA_PARAM, params.replica)
|
|
431
|
-
if (params.params)
|
|
432
|
-
setQueryParam(fetchUrl, WHERE_PARAMS_PARAM, params.params)
|
|
433
|
-
|
|
434
|
-
// Add any remaining custom parameters
|
|
435
|
-
const customParams = { ...params }
|
|
436
|
-
delete customParams.table
|
|
437
|
-
delete customParams.where
|
|
438
|
-
delete customParams.columns
|
|
439
|
-
delete customParams.replica
|
|
440
|
-
delete customParams.params
|
|
441
|
-
|
|
442
|
-
for (const [key, value] of Object.entries(customParams)) {
|
|
443
|
-
setQueryParam(fetchUrl, key, value)
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Add Electric's internal parameters
|
|
448
|
-
fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, this.#lastOffset)
|
|
449
|
-
|
|
450
|
-
if (this.#isUpToDate) {
|
|
451
|
-
if (!this.#isRefreshing) {
|
|
452
|
-
fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`)
|
|
453
|
-
}
|
|
454
|
-
fetchUrl.searchParams.set(
|
|
455
|
-
LIVE_CACHE_BUSTER_QUERY_PARAM,
|
|
456
|
-
this.#liveCacheBuster
|
|
457
|
-
)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (this.#shapeHandle) {
|
|
461
|
-
// This should probably be a header for better cache breaking?
|
|
462
|
-
fetchUrl.searchParams.set(
|
|
463
|
-
SHAPE_HANDLE_QUERY_PARAM,
|
|
464
|
-
this.#shapeHandle!
|
|
465
|
-
)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// sort query params in-place for stable URLs and improved cache hits
|
|
469
|
-
fetchUrl.searchParams.sort()
|
|
470
|
-
|
|
471
|
-
// Create a new AbortController for this request
|
|
472
|
-
this.#requestAbortController = new AbortController()
|
|
473
|
-
|
|
474
|
-
// If user provided a signal, listen to it and pass on the reason for the abort
|
|
475
|
-
let abortListener: (() => void) | undefined
|
|
476
|
-
if (signal) {
|
|
477
|
-
abortListener = () => {
|
|
478
|
-
this.#requestAbortController?.abort(signal.reason)
|
|
479
|
-
}
|
|
480
|
-
signal.addEventListener(`abort`, abortListener, { once: true })
|
|
481
|
-
if (signal.aborted) {
|
|
482
|
-
// If the signal is already aborted, abort the request immediately
|
|
483
|
-
this.#requestAbortController?.abort(signal.reason)
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
let response!: Response
|
|
488
|
-
try {
|
|
489
|
-
response = await this.#fetchClient(fetchUrl.toString(), {
|
|
490
|
-
signal: this.#requestAbortController.signal,
|
|
491
|
-
headers: requestHeaders,
|
|
492
|
-
})
|
|
493
|
-
this.#connected = true
|
|
494
|
-
} catch (e) {
|
|
495
|
-
// Handle abort error triggered by refresh
|
|
496
|
-
if (
|
|
497
|
-
(e instanceof FetchError || e instanceof FetchBackoffAbortError) &&
|
|
498
|
-
this.#requestAbortController.signal.aborted &&
|
|
499
|
-
this.#requestAbortController.signal.reason ===
|
|
500
|
-
FORCE_DISCONNECT_AND_REFRESH
|
|
501
|
-
) {
|
|
502
|
-
// Loop back to the top of the while loop to start a new request
|
|
503
|
-
continue
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (e instanceof FetchBackoffAbortError) break // interrupted
|
|
507
|
-
if (!(e instanceof FetchError)) throw e // should never happen
|
|
508
|
-
|
|
509
|
-
if (e.status == 409) {
|
|
510
|
-
// Upon receiving a 409, we should start from scratch
|
|
511
|
-
// with the newly provided shape handle
|
|
512
|
-
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER]
|
|
513
|
-
this.#reset(newShapeHandle)
|
|
514
|
-
await this.#publish(e.json as Message<T>[])
|
|
515
|
-
continue
|
|
516
|
-
} else {
|
|
517
|
-
// Notify subscribers
|
|
518
|
-
this.#sendErrorToSubscribers(e)
|
|
519
|
-
|
|
520
|
-
// errors that have reached this point are not actionable without
|
|
521
|
-
// additional user input, such as 400s or failures to read the
|
|
522
|
-
// body of a response, so we exit the loop
|
|
523
|
-
throw e
|
|
524
|
-
}
|
|
525
|
-
} finally {
|
|
526
|
-
if (abortListener && signal) {
|
|
527
|
-
signal.removeEventListener(`abort`, abortListener)
|
|
528
|
-
}
|
|
529
|
-
this.#requestAbortController = undefined
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const { headers } = response
|
|
533
|
-
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER)
|
|
534
|
-
if (shapeHandle) {
|
|
535
|
-
this.#shapeHandle = shapeHandle
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER)
|
|
539
|
-
if (lastOffset) {
|
|
540
|
-
this.#lastOffset = lastOffset as Offset
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER)
|
|
544
|
-
if (liveCacheBuster) {
|
|
545
|
-
this.#liveCacheBuster = liveCacheBuster
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const getSchema = (): Schema => {
|
|
549
|
-
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER)
|
|
550
|
-
return schemaHeader ? JSON.parse(schemaHeader) : {}
|
|
551
|
-
}
|
|
552
|
-
this.#schema = this.#schema ?? getSchema()
|
|
553
|
-
|
|
554
|
-
const messages = await response.text()
|
|
555
|
-
const batch = this.#messageParser.parse(messages, this.#schema)
|
|
556
|
-
|
|
557
|
-
// Update isUpToDate
|
|
558
|
-
if (batch.length > 0) {
|
|
559
|
-
const lastMessage = batch[batch.length - 1]
|
|
560
|
-
if (isUpToDateMessage(lastMessage)) {
|
|
561
|
-
this.#lastSyncedAt = Date.now()
|
|
562
|
-
this.#isUpToDate = true
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
await this.#publish(batch)
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
this.#tickPromiseResolver?.()
|
|
569
|
-
}
|
|
403
|
+
await this.#requestShape()
|
|
570
404
|
} catch (err) {
|
|
571
405
|
this.#error = err
|
|
572
406
|
if (this.#onError) {
|
|
@@ -597,6 +431,216 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
597
431
|
}
|
|
598
432
|
}
|
|
599
433
|
|
|
434
|
+
async #requestShape(): Promise<void> {
|
|
435
|
+
if (this.#state === `pause-requested`) {
|
|
436
|
+
this.#state = `paused`
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (
|
|
441
|
+
!this.options.subscribe &&
|
|
442
|
+
(this.options.signal?.aborted || this.#isUpToDate)
|
|
443
|
+
) {
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const resumingFromPause = this.#state === `paused`
|
|
448
|
+
this.#state = `active`
|
|
449
|
+
|
|
450
|
+
const { url, signal } = this.options
|
|
451
|
+
|
|
452
|
+
// Resolve headers and params in parallel
|
|
453
|
+
const [requestHeaders, params] = await Promise.all([
|
|
454
|
+
resolveHeaders(this.options.headers),
|
|
455
|
+
this.options.params
|
|
456
|
+
? toInternalParams(convertWhereParamsToObj(this.options.params))
|
|
457
|
+
: undefined,
|
|
458
|
+
])
|
|
459
|
+
|
|
460
|
+
// Validate params after resolution
|
|
461
|
+
if (params) {
|
|
462
|
+
validateParams(params)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const fetchUrl = new URL(url)
|
|
466
|
+
|
|
467
|
+
// Add PostgreSQL-specific parameters
|
|
468
|
+
if (params) {
|
|
469
|
+
if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table)
|
|
470
|
+
if (params.where) setQueryParam(fetchUrl, WHERE_QUERY_PARAM, params.where)
|
|
471
|
+
if (params.columns)
|
|
472
|
+
setQueryParam(fetchUrl, COLUMNS_QUERY_PARAM, params.columns)
|
|
473
|
+
if (params.replica) setQueryParam(fetchUrl, REPLICA_PARAM, params.replica)
|
|
474
|
+
if (params.params)
|
|
475
|
+
setQueryParam(fetchUrl, WHERE_PARAMS_PARAM, params.params)
|
|
476
|
+
|
|
477
|
+
// Add any remaining custom parameters
|
|
478
|
+
const customParams = { ...params }
|
|
479
|
+
delete customParams.table
|
|
480
|
+
delete customParams.where
|
|
481
|
+
delete customParams.columns
|
|
482
|
+
delete customParams.replica
|
|
483
|
+
delete customParams.params
|
|
484
|
+
|
|
485
|
+
for (const [key, value] of Object.entries(customParams)) {
|
|
486
|
+
setQueryParam(fetchUrl, key, value)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Add Electric's internal parameters
|
|
491
|
+
fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, this.#lastOffset)
|
|
492
|
+
|
|
493
|
+
if (this.#isUpToDate) {
|
|
494
|
+
// If we are resuming from a paused state, we don't want to perform a live request
|
|
495
|
+
// because it could be a long poll that holds for 20sec
|
|
496
|
+
// and during all that time `isConnected` will be false
|
|
497
|
+
if (!this.#isRefreshing && !resumingFromPause) {
|
|
498
|
+
fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`)
|
|
499
|
+
}
|
|
500
|
+
fetchUrl.searchParams.set(
|
|
501
|
+
LIVE_CACHE_BUSTER_QUERY_PARAM,
|
|
502
|
+
this.#liveCacheBuster
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (this.#shapeHandle) {
|
|
507
|
+
// This should probably be a header for better cache breaking?
|
|
508
|
+
fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, this.#shapeHandle!)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// sort query params in-place for stable URLs and improved cache hits
|
|
512
|
+
fetchUrl.searchParams.sort()
|
|
513
|
+
|
|
514
|
+
// Create a new AbortController for this request
|
|
515
|
+
this.#requestAbortController = new AbortController()
|
|
516
|
+
|
|
517
|
+
// If user provided a signal, listen to it and pass on the reason for the abort
|
|
518
|
+
let abortListener: (() => void) | undefined
|
|
519
|
+
if (signal) {
|
|
520
|
+
abortListener = () => {
|
|
521
|
+
this.#requestAbortController?.abort(signal.reason)
|
|
522
|
+
}
|
|
523
|
+
signal.addEventListener(`abort`, abortListener, { once: true })
|
|
524
|
+
if (signal.aborted) {
|
|
525
|
+
// If the signal is already aborted, abort the request immediately
|
|
526
|
+
this.#requestAbortController?.abort(signal.reason)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let response!: Response
|
|
531
|
+
try {
|
|
532
|
+
response = await this.#fetchClient(fetchUrl.toString(), {
|
|
533
|
+
signal: this.#requestAbortController.signal,
|
|
534
|
+
headers: requestHeaders,
|
|
535
|
+
})
|
|
536
|
+
this.#connected = true
|
|
537
|
+
} catch (e) {
|
|
538
|
+
// Handle abort error triggered by refresh
|
|
539
|
+
if (
|
|
540
|
+
(e instanceof FetchError || e instanceof FetchBackoffAbortError) &&
|
|
541
|
+
this.#requestAbortController.signal.aborted &&
|
|
542
|
+
this.#requestAbortController.signal.reason ===
|
|
543
|
+
FORCE_DISCONNECT_AND_REFRESH
|
|
544
|
+
) {
|
|
545
|
+
// Loop back to the top of the while loop to start a new request
|
|
546
|
+
return this.#requestShape()
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (e instanceof FetchBackoffAbortError) {
|
|
550
|
+
if (
|
|
551
|
+
this.#requestAbortController.signal.aborted &&
|
|
552
|
+
this.#requestAbortController.signal.reason === PAUSE_STREAM
|
|
553
|
+
) {
|
|
554
|
+
this.#state = `paused`
|
|
555
|
+
}
|
|
556
|
+
return // interrupted
|
|
557
|
+
}
|
|
558
|
+
if (!(e instanceof FetchError)) throw e // should never happen
|
|
559
|
+
|
|
560
|
+
if (e.status == 409) {
|
|
561
|
+
// Upon receiving a 409, we should start from scratch
|
|
562
|
+
// with the newly provided shape handle
|
|
563
|
+
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER]
|
|
564
|
+
this.#reset(newShapeHandle)
|
|
565
|
+
await this.#publish(e.json as Message<T>[])
|
|
566
|
+
return this.#requestShape()
|
|
567
|
+
} else {
|
|
568
|
+
// Notify subscribers
|
|
569
|
+
this.#sendErrorToSubscribers(e)
|
|
570
|
+
|
|
571
|
+
// errors that have reached this point are not actionable without
|
|
572
|
+
// additional user input, such as 400s or failures to read the
|
|
573
|
+
// body of a response, so we exit the loop
|
|
574
|
+
throw e
|
|
575
|
+
}
|
|
576
|
+
} finally {
|
|
577
|
+
if (abortListener && signal) {
|
|
578
|
+
signal.removeEventListener(`abort`, abortListener)
|
|
579
|
+
}
|
|
580
|
+
this.#requestAbortController = undefined
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const { headers, status } = response
|
|
584
|
+
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER)
|
|
585
|
+
if (shapeHandle) {
|
|
586
|
+
this.#shapeHandle = shapeHandle
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER)
|
|
590
|
+
if (lastOffset) {
|
|
591
|
+
this.#lastOffset = lastOffset as Offset
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER)
|
|
595
|
+
if (liveCacheBuster) {
|
|
596
|
+
this.#liveCacheBuster = liveCacheBuster
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const getSchema = (): Schema => {
|
|
600
|
+
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER)
|
|
601
|
+
return schemaHeader ? JSON.parse(schemaHeader) : {}
|
|
602
|
+
}
|
|
603
|
+
this.#schema = this.#schema ?? getSchema()
|
|
604
|
+
|
|
605
|
+
// NOTE: 204s are deprecated, the Electric server should not
|
|
606
|
+
// send these in latest versions but this is here for backwards
|
|
607
|
+
// compatibility
|
|
608
|
+
if (status === 204) {
|
|
609
|
+
// There's no content so we are live and up to date
|
|
610
|
+
this.#lastSyncedAt = Date.now()
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const messages = (await response.text()) || `[]`
|
|
614
|
+
const batch = this.#messageParser.parse(messages, this.#schema)
|
|
615
|
+
|
|
616
|
+
// Update isUpToDate
|
|
617
|
+
if (batch.length > 0) {
|
|
618
|
+
const lastMessage = batch[batch.length - 1]
|
|
619
|
+
if (isUpToDateMessage(lastMessage)) {
|
|
620
|
+
this.#lastSyncedAt = Date.now()
|
|
621
|
+
this.#isUpToDate = true
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
await this.#publish(batch)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
this.#tickPromiseResolver?.()
|
|
628
|
+
return this.#requestShape()
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
#pause() {
|
|
632
|
+
if (this.#started && this.#state === `active`) {
|
|
633
|
+
this.#state = `pause-requested`
|
|
634
|
+
this.#requestAbortController?.abort(PAUSE_STREAM)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
#resume() {
|
|
639
|
+
if (this.#started && this.#state === `paused`) {
|
|
640
|
+
this.#start()
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
600
644
|
subscribe(
|
|
601
645
|
callback: (messages: Message<T>[]) => MaybePromise<void>,
|
|
602
646
|
onError: (error: Error) => void = () => {}
|
|
@@ -640,6 +684,10 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
640
684
|
return this.#started
|
|
641
685
|
}
|
|
642
686
|
|
|
687
|
+
isPaused(): boolean {
|
|
688
|
+
return this.#state === `paused`
|
|
689
|
+
}
|
|
690
|
+
|
|
643
691
|
/** Await the next tick of the request loop */
|
|
644
692
|
async #nextTick() {
|
|
645
693
|
if (this.#tickPromise) {
|
|
@@ -694,6 +742,24 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
694
742
|
})
|
|
695
743
|
}
|
|
696
744
|
|
|
745
|
+
#subscribeToVisibilityChanges() {
|
|
746
|
+
if (
|
|
747
|
+
typeof document === `object` &&
|
|
748
|
+
typeof document.hidden === `boolean` &&
|
|
749
|
+
typeof document.addEventListener === `function`
|
|
750
|
+
) {
|
|
751
|
+
const visibilityHandler = () => {
|
|
752
|
+
if (document.hidden) {
|
|
753
|
+
this.#pause()
|
|
754
|
+
} else {
|
|
755
|
+
this.#resume()
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
document.addEventListener(`visibilitychange`, visibilityHandler)
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
697
763
|
/**
|
|
698
764
|
* Resets the state of the stream, optionally with a provided
|
|
699
765
|
* shape handle
|
package/src/constants.ts
CHANGED
package/src/fetch.ts
CHANGED
|
@@ -95,13 +95,18 @@ export function createFetchWithBackoff(
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
const NO_BODY_STATUS_CODES = [201, 204, 205]
|
|
99
|
+
|
|
98
100
|
// Ensure body can actually be read in its entirety
|
|
99
101
|
export function createFetchWithConsumedMessages(fetchClient: typeof fetch) {
|
|
100
102
|
return async (...args: Parameters<typeof fetch>): Promise<Response> => {
|
|
101
103
|
const url = args[0]
|
|
102
104
|
const res = await fetchClient(...args)
|
|
103
105
|
try {
|
|
104
|
-
if (res.
|
|
106
|
+
if (res.status < 200 || NO_BODY_STATUS_CODES.includes(res.status)) {
|
|
107
|
+
return res
|
|
108
|
+
}
|
|
109
|
+
|
|
105
110
|
const text = await res.text()
|
|
106
111
|
return new Response(text, res)
|
|
107
112
|
} catch (err) {
|