@electric-sql/client 1.0.11 → 1.0.13

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/src/client.ts CHANGED
@@ -9,7 +9,13 @@ import {
9
9
  SnapshotMetadata,
10
10
  } from './types'
11
11
  import { MessageParser, Parser, TransformFunction } from './parser'
12
- import { getOffset, isUpToDateMessage, isChangeMessage } from './helpers'
12
+ import {
13
+ getOffset,
14
+ isUpToDateMessage,
15
+ isChangeMessage,
16
+ applySubdomainSharding,
17
+ ShardSubdomainOption,
18
+ } from './helpers'
13
19
  import {
14
20
  FetchError,
15
21
  FetchBackoffAbortError,
@@ -280,7 +286,44 @@ export interface ShapeStreamOptions<T = never> {
280
286
  /**
281
287
  * Initial data loading mode
282
288
  */
283
- mode?: LogMode
289
+ log?: LogMode
290
+
291
+ /**
292
+ * Enable subdomain sharding to bypass browser HTTP/1.1 connection limits.
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
325
+ */
326
+ shardSubdomain?: ShardSubdomainOption
284
327
 
285
328
  signal?: AbortSignal
286
329
  fetchClient?: typeof fetch
@@ -439,6 +482,10 @@ export class ShapeStream<T extends Row<unknown> = Row>
439
482
  constructor(options: ShapeStreamOptions<GetExtensions<T>>) {
440
483
  this.options = { subscribe: true, ...options }
441
484
  validateOptions(this.options)
485
+ this.options.url = applySubdomainSharding(
486
+ this.options.url,
487
+ this.options.shardSubdomain
488
+ )
442
489
  this.#lastOffset = this.options.offset ?? `-1`
443
490
  this.#liveCacheBuster = ``
444
491
  this.#shapeHandle = this.options.handle
@@ -447,7 +494,7 @@ export class ShapeStream<T extends Row<unknown> = Row>
447
494
  options.transformer
448
495
  )
449
496
  this.#onError = this.options.onError
450
- this.#mode = this.options.mode ?? `full`
497
+ this.#mode = this.options.log ?? `full`
451
498
 
452
499
  const baseFetchClient =
453
500
  options.fetchClient ??
package/src/helpers.ts CHANGED
@@ -97,3 +97,41 @@ 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
+ }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export {
5
5
  isChangeMessage,
6
6
  isControlMessage,
7
7
  isVisibleInSnapshot,
8
+ type ShardSubdomainOption,
8
9
  } from './helpers'
9
10
  export { FetchError } from './error'
10
11
  export { type BackoffOptions, BackoffDefaults } from './fetch'