@electric-sql/client 1.0.3 → 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 +168 -125
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/index.browser.mjs +5 -5
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.legacy-esm.js +166 -125
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +168 -125
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +238 -180
- package/src/constants.ts +1 -0
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,189 +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, status } = 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
|
-
// NOTE: 204s are deprecated, the Electric server should not
|
|
555
|
-
// send these in latest versions but this is here for backwards
|
|
556
|
-
// compatibility
|
|
557
|
-
if (status === 204) {
|
|
558
|
-
// There's no content so we are live and up to date
|
|
559
|
-
this.#lastSyncedAt = Date.now()
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
const messages = (await response.text()) || `[]`
|
|
563
|
-
const batch = this.#messageParser.parse(messages, this.#schema)
|
|
564
|
-
|
|
565
|
-
// Update isUpToDate
|
|
566
|
-
if (batch.length > 0) {
|
|
567
|
-
const lastMessage = batch[batch.length - 1]
|
|
568
|
-
if (isUpToDateMessage(lastMessage)) {
|
|
569
|
-
this.#lastSyncedAt = Date.now()
|
|
570
|
-
this.#isUpToDate = true
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
await this.#publish(batch)
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
this.#tickPromiseResolver?.()
|
|
577
|
-
}
|
|
403
|
+
await this.#requestShape()
|
|
578
404
|
} catch (err) {
|
|
579
405
|
this.#error = err
|
|
580
406
|
if (this.#onError) {
|
|
@@ -605,6 +431,216 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
605
431
|
}
|
|
606
432
|
}
|
|
607
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
|
+
|
|
608
644
|
subscribe(
|
|
609
645
|
callback: (messages: Message<T>[]) => MaybePromise<void>,
|
|
610
646
|
onError: (error: Error) => void = () => {}
|
|
@@ -648,6 +684,10 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
648
684
|
return this.#started
|
|
649
685
|
}
|
|
650
686
|
|
|
687
|
+
isPaused(): boolean {
|
|
688
|
+
return this.#state === `paused`
|
|
689
|
+
}
|
|
690
|
+
|
|
651
691
|
/** Await the next tick of the request loop */
|
|
652
692
|
async #nextTick() {
|
|
653
693
|
if (this.#tickPromise) {
|
|
@@ -702,6 +742,24 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
702
742
|
})
|
|
703
743
|
}
|
|
704
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
|
+
|
|
705
763
|
/**
|
|
706
764
|
* Resets the state of the stream, optionally with a provided
|
|
707
765
|
* shape handle
|
package/src/constants.ts
CHANGED