@electric-sql/client 1.4.2 → 1.5.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@electric-sql/client",
3
3
  "description": "Postgres everywhere - your data, in sync, wherever you need it.",
4
- "version": "1.4.2",
4
+ "version": "1.5.0",
5
5
  "author": "ElectricSQL team and contributors.",
6
6
  "bugs": {
7
7
  "url": "https://github.com/electric-sql/electric/issues"
package/src/client.ts CHANGED
@@ -429,6 +429,27 @@ export interface ShapeStreamOptions<T = never> {
429
429
  * ```
430
430
  */
431
431
  onError?: ShapeStreamErrorHandler
432
+
433
+ /**
434
+ * HTTP method to use for subset snapshot requests (`requestSnapshot`/`fetchSnapshot`).
435
+ *
436
+ * - `'GET'` (default): Sends subset params as URL query parameters. May fail with
437
+ * HTTP 414 errors for large queries with many parameters.
438
+ * - `'POST'`: Sends subset params in request body as JSON. Recommended for queries
439
+ * with large parameter lists (e.g., `WHERE id = ANY($1)` with hundreds of IDs).
440
+ *
441
+ * This can be overridden per-request by passing `method` in the subset params.
442
+ *
443
+ * @example
444
+ * ```typescript
445
+ * const stream = new ShapeStream({
446
+ * url: 'http://localhost:3000/v1/shape',
447
+ * params: { table: 'items' },
448
+ * subsetMethod: 'POST', // Use POST for all subset requests
449
+ * })
450
+ * ```
451
+ */
452
+ subsetMethod?: `GET` | `POST`
432
453
  }
433
454
 
434
455
  export interface ShapeStreamInterface<T extends Row<unknown> = Row> {
@@ -1632,6 +1653,10 @@ export class ShapeStream<T extends Row<unknown> = Row>
1632
1653
  * Fetch a snapshot for subset of data.
1633
1654
  * Returns the metadata and the data, but does not inject it into the subscribed data stream.
1634
1655
  *
1656
+ * By default, uses GET to send subset parameters as query parameters. This may hit URL length
1657
+ * limits (HTTP 414) with large WHERE clauses or many parameters. Set `method: 'POST'` or use
1658
+ * `subsetMethod: 'POST'` on the stream to send parameters in the request body instead.
1659
+ *
1635
1660
  * @param opts - The options for the snapshot request.
1636
1661
  * @returns The metadata and the data for the snapshot.
1637
1662
  */
@@ -1639,27 +1664,35 @@ export class ShapeStream<T extends Row<unknown> = Row>
1639
1664
  metadata: SnapshotMetadata
1640
1665
  data: Array<ChangeMessage<T>>
1641
1666
  }> {
1642
- const { fetchUrl, requestHeaders } = await this.#constructUrl(
1643
- this.options.url,
1644
- true,
1645
- opts
1646
- )
1667
+ const method = opts.method ?? this.options.subsetMethod ?? `GET`
1668
+ const usePost = method === `POST`
1669
+
1670
+ let fetchUrl: URL
1671
+ let fetchOptions: RequestInit
1672
+
1673
+ if (usePost) {
1674
+ const result = await this.#constructUrl(this.options.url, true)
1675
+ fetchUrl = result.fetchUrl
1676
+ fetchOptions = {
1677
+ method: `POST`,
1678
+ headers: {
1679
+ ...result.requestHeaders,
1680
+ 'Content-Type': `application/json`,
1681
+ },
1682
+ body: JSON.stringify(this.#buildSubsetBody(opts)),
1683
+ }
1684
+ } else {
1685
+ const result = await this.#constructUrl(this.options.url, true, opts)
1686
+ fetchUrl = result.fetchUrl
1687
+ fetchOptions = { headers: result.requestHeaders }
1688
+ }
1647
1689
 
1648
- const response = await this.#fetchClient(fetchUrl.toString(), {
1649
- headers: requestHeaders,
1650
- })
1690
+ const response = await this.#fetchClient(fetchUrl.toString(), fetchOptions)
1651
1691
 
1652
1692
  if (!response.ok) {
1653
- throw new FetchError(
1654
- response.status,
1655
- undefined,
1656
- undefined,
1657
- Object.fromEntries([...response.headers.entries()]),
1658
- fetchUrl.toString()
1659
- )
1693
+ throw await FetchError.fromResponse(response, fetchUrl.toString())
1660
1694
  }
1661
1695
 
1662
- // Use schema from stream if available, otherwise extract from response header
1663
1696
  const schema: Schema =
1664
1697
  this.#schema ??
1665
1698
  getSchemaFromHeaders(response.headers, {
@@ -1673,10 +1706,51 @@ export class ShapeStream<T extends Row<unknown> = Row>
1673
1706
  schema
1674
1707
  )
1675
1708
 
1676
- return {
1677
- metadata,
1678
- data,
1709
+ return { metadata, data }
1710
+ }
1711
+
1712
+ #buildSubsetBody(opts: SubsetParams): Record<string, unknown> {
1713
+ const body: Record<string, unknown> = {}
1714
+
1715
+ if (opts.whereExpr) {
1716
+ body.where = compileExpression(
1717
+ opts.whereExpr,
1718
+ this.options.columnMapper?.encode
1719
+ )
1720
+ body.where_expr = opts.whereExpr
1721
+ } else if (opts.where && typeof opts.where === `string`) {
1722
+ body.where = encodeWhereClause(
1723
+ opts.where,
1724
+ this.options.columnMapper?.encode
1725
+ )
1726
+ }
1727
+
1728
+ if (opts.params) {
1729
+ body.params = opts.params
1679
1730
  }
1731
+
1732
+ if (opts.limit !== undefined) {
1733
+ body.limit = opts.limit
1734
+ }
1735
+
1736
+ if (opts.offset !== undefined) {
1737
+ body.offset = opts.offset
1738
+ }
1739
+
1740
+ if (opts.orderByExpr) {
1741
+ body.order_by = compileOrderBy(
1742
+ opts.orderByExpr,
1743
+ this.options.columnMapper?.encode
1744
+ )
1745
+ body.order_by_expr = opts.orderByExpr
1746
+ } else if (opts.orderBy && typeof opts.orderBy === `string`) {
1747
+ body.order_by = encodeWhereClause(
1748
+ opts.orderBy,
1749
+ this.options.columnMapper?.encode
1750
+ )
1751
+ }
1752
+
1753
+ return body
1680
1754
  }
1681
1755
  }
1682
1756
 
package/src/types.ts CHANGED
@@ -102,6 +102,16 @@ export type SubsetParams = {
102
102
  whereExpr?: SerializedExpression
103
103
  /** Structured ORDER BY clauses (preferred when available) */
104
104
  orderByExpr?: SerializedOrderByClause[]
105
+ /**
106
+ * HTTP method to use for the request. Overrides `subsetMethod` from ShapeStreamOptions.
107
+ * - `GET` (default): Sends subset params as query parameters. May fail with 414 errors
108
+ * for large queries.
109
+ * - `POST`: Sends subset params in request body as JSON. Recommended to avoid URL
110
+ * length limits with large WHERE clauses or many parameters.
111
+ *
112
+ * In Electric 2.0, GET will be deprecated and only POST will be supported.
113
+ */
114
+ method?: `GET` | `POST`
105
115
  }
106
116
 
107
117
  export type ControlMessage = {