@electric-sql/client 1.0.13 → 1.1.0
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 +10 -26
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +52 -85
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +52 -85
- package/dist/index.legacy-esm.js +10 -26
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +10 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +18 -51
- package/src/constants.ts +4 -0
- package/src/fetch.ts +4 -6
- package/src/helpers.ts +0 -38
- package/src/index.ts +0 -1
package/src/client.ts
CHANGED
|
@@ -9,13 +9,7 @@ import {
|
|
|
9
9
|
SnapshotMetadata,
|
|
10
10
|
} from './types'
|
|
11
11
|
import { MessageParser, Parser, TransformFunction } from './parser'
|
|
12
|
-
import {
|
|
13
|
-
getOffset,
|
|
14
|
-
isUpToDateMessage,
|
|
15
|
-
isChangeMessage,
|
|
16
|
-
applySubdomainSharding,
|
|
17
|
-
ShardSubdomainOption,
|
|
18
|
-
} from './helpers'
|
|
12
|
+
import { getOffset, isUpToDateMessage, isChangeMessage } from './helpers'
|
|
19
13
|
import {
|
|
20
14
|
FetchError,
|
|
21
15
|
FetchBackoffAbortError,
|
|
@@ -50,6 +44,7 @@ import {
|
|
|
50
44
|
FORCE_DISCONNECT_AND_REFRESH,
|
|
51
45
|
PAUSE_STREAM,
|
|
52
46
|
EXPERIMENTAL_LIVE_SSE_QUERY_PARAM,
|
|
47
|
+
LIVE_SSE_QUERY_PARAM,
|
|
53
48
|
ELECTRIC_PROTOCOL_QUERY_PARAMS,
|
|
54
49
|
LOG_MODE_QUERY_PARAM,
|
|
55
50
|
SUBSET_PARAM_WHERE,
|
|
@@ -279,51 +274,19 @@ export interface ShapeStreamOptions<T = never> {
|
|
|
279
274
|
subscribe?: boolean
|
|
280
275
|
|
|
281
276
|
/**
|
|
282
|
-
*
|
|
277
|
+
* @deprecated No longer experimental, use {@link liveSse} instead.
|
|
283
278
|
*/
|
|
284
279
|
experimentalLiveSse?: boolean
|
|
285
280
|
|
|
286
281
|
/**
|
|
287
|
-
*
|
|
282
|
+
* Use Server-Sent Events (SSE) for live updates.
|
|
288
283
|
*/
|
|
289
|
-
|
|
284
|
+
liveSse?: boolean
|
|
290
285
|
|
|
291
286
|
/**
|
|
292
|
-
*
|
|
293
|
-
* This is useful in local development and is enabled by default for localhost URLs.
|
|
294
|
-
*
|
|
295
|
-
* See https://electric-sql.com/docs/guides/troubleshooting#slow-shapes-mdash-why-are-my-shapes-slow-in-the-browser-in-local-development
|
|
296
|
-
*
|
|
297
|
-
* When sharded, each shape stream gets a unique subdomain (e.g., `a7f2c.localhost`),
|
|
298
|
-
* which bypasses the browser HTTP/1.1 connection limits. This avoids the need to serve
|
|
299
|
-
* the development server over HTTP/2 (and thus HTTPS) in development.
|
|
300
|
-
*
|
|
301
|
-
* Options:
|
|
302
|
-
* - `'localhost'` - Automatically shard `localhost` and `*.localhost` URLs (the default)
|
|
303
|
-
* - `'always'` - Shard URLs regardless of the hostname
|
|
304
|
-
* - `'never'` - Disable sharding
|
|
305
|
-
* - `true` - Alias for `'always'`
|
|
306
|
-
* - `false` - Alias for `'never'`
|
|
307
|
-
*
|
|
308
|
-
* @default 'localhost'
|
|
309
|
-
*
|
|
310
|
-
* @example
|
|
311
|
-
* { url: 'http://localhost:3000/v1/shape', shardSubdomain: 'localhost' }
|
|
312
|
-
* // → http://a1c2f.localhost:3000/v1/shape
|
|
313
|
-
*
|
|
314
|
-
* @example
|
|
315
|
-
* { url: 'https://api.example.com', shardSubdomain: 'localhost' }
|
|
316
|
-
* // → https://api.example.com
|
|
317
|
-
*
|
|
318
|
-
* @example
|
|
319
|
-
* { url: 'https://localhost:3000', shardSubdomain: 'never' }
|
|
320
|
-
* // → https://localhost:3000
|
|
321
|
-
*
|
|
322
|
-
* @example
|
|
323
|
-
* { url: 'https://api.example.com', shardSubdomain: 'always' }
|
|
324
|
-
* // → https://b2d3g.api.example.com
|
|
287
|
+
* Initial data loading mode
|
|
325
288
|
*/
|
|
326
|
-
|
|
289
|
+
log?: LogMode
|
|
327
290
|
|
|
328
291
|
signal?: AbortSignal
|
|
329
292
|
fetchClient?: typeof fetch
|
|
@@ -415,7 +378,7 @@ function canonicalShapeKey(url: URL): string {
|
|
|
415
378
|
* ```
|
|
416
379
|
* const stream = new ShapeStream({
|
|
417
380
|
* url: `http://localhost:3000/v1/shape`,
|
|
418
|
-
*
|
|
381
|
+
* liveSse: true
|
|
419
382
|
* })
|
|
420
383
|
* ```
|
|
421
384
|
*
|
|
@@ -482,10 +445,6 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
482
445
|
constructor(options: ShapeStreamOptions<GetExtensions<T>>) {
|
|
483
446
|
this.options = { subscribe: true, ...options }
|
|
484
447
|
validateOptions(this.options)
|
|
485
|
-
this.options.url = applySubdomainSharding(
|
|
486
|
-
this.options.url,
|
|
487
|
-
this.options.shardSubdomain
|
|
488
|
-
)
|
|
489
448
|
this.#lastOffset = this.options.offset ?? `-1`
|
|
490
449
|
this.#liveCacheBuster = ``
|
|
491
450
|
this.#shapeHandle = this.options.handle
|
|
@@ -645,7 +604,13 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
645
604
|
const newShapeHandle =
|
|
646
605
|
e.headers[SHAPE_HANDLE_HEADER] || `${this.#shapeHandle!}-next`
|
|
647
606
|
this.#reset(newShapeHandle)
|
|
648
|
-
|
|
607
|
+
|
|
608
|
+
// must refetch control message might be in a list or not depending
|
|
609
|
+
// on whether it came from an SSE request or long poll - handle both
|
|
610
|
+
// cases for safety here but worth revisiting 409 handling
|
|
611
|
+
await this.#publish(
|
|
612
|
+
(Array.isArray(e.json) ? e.json : [e.json]) as Message<T>[]
|
|
613
|
+
)
|
|
649
614
|
return this.#requestShape()
|
|
650
615
|
} else {
|
|
651
616
|
// Notify subscribers
|
|
@@ -862,13 +827,15 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
862
827
|
headers: Record<string, string>
|
|
863
828
|
resumingFromPause?: boolean
|
|
864
829
|
}): Promise<void> {
|
|
830
|
+
const useSse = this.options.liveSse ?? this.options.experimentalLiveSse
|
|
865
831
|
if (
|
|
866
832
|
this.#isUpToDate &&
|
|
867
|
-
|
|
833
|
+
useSse &&
|
|
868
834
|
!this.#isRefreshing &&
|
|
869
835
|
!opts.resumingFromPause
|
|
870
836
|
) {
|
|
871
837
|
opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`)
|
|
838
|
+
opts.fetchUrl.searchParams.set(LIVE_SSE_QUERY_PARAM, `true`)
|
|
872
839
|
return this.#requestShapeSSE(opts)
|
|
873
840
|
}
|
|
874
841
|
|
package/src/constants.ts
CHANGED
|
@@ -13,7 +13,11 @@ export const TABLE_QUERY_PARAM = `table`
|
|
|
13
13
|
export const WHERE_QUERY_PARAM = `where`
|
|
14
14
|
export const REPLICA_PARAM = `replica`
|
|
15
15
|
export const WHERE_PARAMS_PARAM = `params`
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use {@link LIVE_SSE_QUERY_PARAM} instead.
|
|
18
|
+
*/
|
|
16
19
|
export const EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`
|
|
20
|
+
export const LIVE_SSE_QUERY_PARAM = `live_sse`
|
|
17
21
|
export const FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`
|
|
18
22
|
export const PAUSE_STREAM = `pause-stream`
|
|
19
23
|
export const LOG_MODE_QUERY_PARAM = `log`
|
package/src/fetch.ts
CHANGED
|
@@ -59,13 +59,7 @@ export function createFetchWithBackoff(
|
|
|
59
59
|
let delay = initialDelay
|
|
60
60
|
let attempt = 0
|
|
61
61
|
|
|
62
|
-
/* eslint-disable no-constant-condition -- we re-fetch the shape log
|
|
63
|
-
* continuously until we get a non-ok response. For recoverable errors,
|
|
64
|
-
* we retry the fetch with exponential backoff. Users can pass in an
|
|
65
|
-
* AbortController to abort the fetching an any point.
|
|
66
|
-
* */
|
|
67
62
|
while (true) {
|
|
68
|
-
/* eslint-enable no-constant-condition */
|
|
69
63
|
try {
|
|
70
64
|
const result = await fetchClient(...args)
|
|
71
65
|
if (result.ok) return result
|
|
@@ -118,6 +112,10 @@ export function createFetchWithConsumedMessages(fetchClient: typeof fetch) {
|
|
|
118
112
|
const text = await res.text()
|
|
119
113
|
return new Response(text, res)
|
|
120
114
|
} catch (err) {
|
|
115
|
+
if (args[1]?.signal?.aborted) {
|
|
116
|
+
throw new FetchBackoffAbortError()
|
|
117
|
+
}
|
|
118
|
+
|
|
121
119
|
throw new FetchError(
|
|
122
120
|
res.status,
|
|
123
121
|
undefined,
|
package/src/helpers.ts
CHANGED
|
@@ -97,41 +97,3 @@ export function isVisibleInSnapshot(
|
|
|
97
97
|
|
|
98
98
|
return xid < xmin || (xid < xmax && !xip.includes(xid))
|
|
99
99
|
}
|
|
100
|
-
|
|
101
|
-
export function generateShardId(): string {
|
|
102
|
-
return Math.floor(Math.random() * 0xfffff)
|
|
103
|
-
.toString(16)
|
|
104
|
-
.padStart(5, `0`)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function isLocalhostUrl(url: URL): boolean {
|
|
108
|
-
const hostname = url.hostname.toLowerCase()
|
|
109
|
-
return hostname === `localhost` || hostname.endsWith(`.localhost`)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export type ShardSubdomainOption = `always` | `localhost` | `never` | boolean
|
|
113
|
-
|
|
114
|
-
export function applySubdomainSharding(
|
|
115
|
-
originalUrl: string,
|
|
116
|
-
option: ShardSubdomainOption = `localhost`
|
|
117
|
-
): string {
|
|
118
|
-
if (option === `never` || option === false) {
|
|
119
|
-
return originalUrl
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const url = new URL(originalUrl)
|
|
123
|
-
|
|
124
|
-
const shouldShard =
|
|
125
|
-
option === `always` ||
|
|
126
|
-
option === true ||
|
|
127
|
-
(option === `localhost` && isLocalhostUrl(url))
|
|
128
|
-
|
|
129
|
-
if (!shouldShard) {
|
|
130
|
-
return originalUrl
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const shardId = generateShardId()
|
|
134
|
-
url.hostname = `${shardId}.${url.hostname}`
|
|
135
|
-
|
|
136
|
-
return url.toString()
|
|
137
|
-
}
|