@electric-sql/client 1.4.2 → 1.5.1
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 +83 -19
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +34 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.legacy-esm.js +83 -19
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +83 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +121 -20
- package/src/types.ts +10 -0
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,16 @@ type SubsetParams = {
|
|
|
84
84
|
whereExpr?: SerializedExpression;
|
|
85
85
|
/** Structured ORDER BY clauses (preferred when available) */
|
|
86
86
|
orderByExpr?: SerializedOrderByClause[];
|
|
87
|
+
/**
|
|
88
|
+
* HTTP method to use for the request. Overrides `subsetMethod` from ShapeStreamOptions.
|
|
89
|
+
* - `GET` (default): Sends subset params as query parameters. May fail with 414 errors
|
|
90
|
+
* for large queries.
|
|
91
|
+
* - `POST`: Sends subset params in request body as JSON. Recommended to avoid URL
|
|
92
|
+
* length limits with large WHERE clauses or many parameters.
|
|
93
|
+
*
|
|
94
|
+
* In Electric 2.0, GET will be deprecated and only POST will be supported.
|
|
95
|
+
*/
|
|
96
|
+
method?: `GET` | `POST`;
|
|
87
97
|
};
|
|
88
98
|
type ControlMessage = {
|
|
89
99
|
headers: (Header & {
|
|
@@ -629,6 +639,26 @@ interface ShapeStreamOptions<T = never> {
|
|
|
629
639
|
* ```
|
|
630
640
|
*/
|
|
631
641
|
onError?: ShapeStreamErrorHandler;
|
|
642
|
+
/**
|
|
643
|
+
* HTTP method to use for subset snapshot requests (`requestSnapshot`/`fetchSnapshot`).
|
|
644
|
+
*
|
|
645
|
+
* - `'GET'` (default): Sends subset params as URL query parameters. May fail with
|
|
646
|
+
* HTTP 414 errors for large queries with many parameters.
|
|
647
|
+
* - `'POST'`: Sends subset params in request body as JSON. Recommended for queries
|
|
648
|
+
* with large parameter lists (e.g., `WHERE id = ANY($1)` with hundreds of IDs).
|
|
649
|
+
*
|
|
650
|
+
* This can be overridden per-request by passing `method` in the subset params.
|
|
651
|
+
*
|
|
652
|
+
* @example
|
|
653
|
+
* ```typescript
|
|
654
|
+
* const stream = new ShapeStream({
|
|
655
|
+
* url: 'http://localhost:3000/v1/shape',
|
|
656
|
+
* params: { table: 'items' },
|
|
657
|
+
* subsetMethod: 'POST', // Use POST for all subset requests
|
|
658
|
+
* })
|
|
659
|
+
* ```
|
|
660
|
+
*/
|
|
661
|
+
subsetMethod?: `GET` | `POST`;
|
|
632
662
|
}
|
|
633
663
|
interface ShapeStreamInterface<T extends Row<unknown> = Row> {
|
|
634
664
|
subscribe(callback: (messages: Message<T>[]) => MaybePromise<void> | {
|
|
@@ -748,6 +778,10 @@ declare class ShapeStream<T extends Row<unknown> = Row> implements ShapeStreamIn
|
|
|
748
778
|
* Fetch a snapshot for subset of data.
|
|
749
779
|
* Returns the metadata and the data, but does not inject it into the subscribed data stream.
|
|
750
780
|
*
|
|
781
|
+
* By default, uses GET to send subset parameters as query parameters. This may hit URL length
|
|
782
|
+
* limits (HTTP 414) with large WHERE clauses or many parameters. Set `method: 'POST'` or use
|
|
783
|
+
* `subsetMethod: 'POST'` on the stream to send parameters in the request body instead.
|
|
784
|
+
*
|
|
751
785
|
* @param opts - The options for the snapshot request.
|
|
752
786
|
* @returns The metadata and the data for the snapshot.
|
|
753
787
|
*/
|
package/dist/index.legacy-esm.js
CHANGED
|
@@ -1142,7 +1142,7 @@ function canonicalShapeKey(url) {
|
|
|
1142
1142
|
cleanUrl.searchParams.sort();
|
|
1143
1143
|
return cleanUrl.toString();
|
|
1144
1144
|
}
|
|
1145
|
-
var _error, _fetchClient2, _sseFetchClient, _messageParser, _subscribers, _started, _state, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _isMidStream, _connected, _shapeHandle, _mode, _schema, _onError, _requestAbortController, _isRefreshing, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _messageChain, _snapshotTracker, _activeSnapshotRequests, _midStreamPromise, _midStreamPromiseResolver, _lastSeenCursor, _currentFetchUrl, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _staleCacheBuster, _staleCacheRetryCount, _maxStaleCacheRetries, _ShapeStream_instances, replayMode_get, start_fn, requestShape_fn, constructUrl_fn, createAbortListener_fn, onInitialResponse_fn, onMessages_fn, fetchShape_fn, requestShapeLongPoll_fn, requestShapeSSE_fn, pause_fn, resume_fn, nextTick_fn, waitForStreamEnd_fn, publish_fn, sendErrorToSubscribers_fn, subscribeToVisibilityChanges_fn, reset_fn;
|
|
1145
|
+
var _error, _fetchClient2, _sseFetchClient, _messageParser, _subscribers, _started, _state, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _isMidStream, _connected, _shapeHandle, _mode, _schema, _onError, _requestAbortController, _isRefreshing, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _messageChain, _snapshotTracker, _activeSnapshotRequests, _midStreamPromise, _midStreamPromiseResolver, _lastSeenCursor, _currentFetchUrl, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _staleCacheBuster, _staleCacheRetryCount, _maxStaleCacheRetries, _ShapeStream_instances, replayMode_get, start_fn, requestShape_fn, constructUrl_fn, createAbortListener_fn, onInitialResponse_fn, onMessages_fn, fetchShape_fn, requestShapeLongPoll_fn, requestShapeSSE_fn, pause_fn, resume_fn, nextTick_fn, waitForStreamEnd_fn, publish_fn, sendErrorToSubscribers_fn, subscribeToVisibilityChanges_fn, reset_fn, buildSubsetBody_fn;
|
|
1146
1146
|
var ShapeStream = class {
|
|
1147
1147
|
constructor(options) {
|
|
1148
1148
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -1357,25 +1357,53 @@ var ShapeStream = class {
|
|
|
1357
1357
|
* Fetch a snapshot for subset of data.
|
|
1358
1358
|
* Returns the metadata and the data, but does not inject it into the subscribed data stream.
|
|
1359
1359
|
*
|
|
1360
|
+
* By default, uses GET to send subset parameters as query parameters. This may hit URL length
|
|
1361
|
+
* limits (HTTP 414) with large WHERE clauses or many parameters. Set `method: 'POST'` or use
|
|
1362
|
+
* `subsetMethod: 'POST'` on the stream to send parameters in the request body instead.
|
|
1363
|
+
*
|
|
1360
1364
|
* @param opts - The options for the snapshot request.
|
|
1361
1365
|
* @returns The metadata and the data for the snapshot.
|
|
1362
1366
|
*/
|
|
1363
1367
|
async fetchSnapshot(opts) {
|
|
1364
|
-
var _a;
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1368
|
+
var _a, _b, _c;
|
|
1369
|
+
const method = (_b = (_a = opts.method) != null ? _a : this.options.subsetMethod) != null ? _b : `GET`;
|
|
1370
|
+
const usePost = method === `POST`;
|
|
1371
|
+
let fetchUrl;
|
|
1372
|
+
let fetchOptions;
|
|
1373
|
+
if (usePost) {
|
|
1374
|
+
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true);
|
|
1375
|
+
fetchUrl = result.fetchUrl;
|
|
1376
|
+
fetchOptions = {
|
|
1377
|
+
method: `POST`,
|
|
1378
|
+
headers: __spreadProps(__spreadValues({}, result.requestHeaders), {
|
|
1379
|
+
"Content-Type": `application/json`
|
|
1380
|
+
}),
|
|
1381
|
+
body: JSON.stringify(__privateMethod(this, _ShapeStream_instances, buildSubsetBody_fn).call(this, opts))
|
|
1382
|
+
};
|
|
1383
|
+
} else {
|
|
1384
|
+
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
|
|
1385
|
+
fetchUrl = result.fetchUrl;
|
|
1386
|
+
fetchOptions = { headers: result.requestHeaders };
|
|
1387
|
+
}
|
|
1388
|
+
const usedHandle = __privateGet(this, _shapeHandle);
|
|
1389
|
+
let response;
|
|
1390
|
+
try {
|
|
1391
|
+
response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
|
|
1392
|
+
} catch (e) {
|
|
1393
|
+
if (e instanceof FetchError && e.status === 409) {
|
|
1394
|
+
if (usedHandle) {
|
|
1395
|
+
const shapeKey = canonicalShapeKey(fetchUrl);
|
|
1396
|
+
expiredShapesCache.markExpired(shapeKey, usedHandle);
|
|
1397
|
+
}
|
|
1398
|
+
__privateSet(this, _shapeHandle, e.headers[SHAPE_HANDLE_HEADER] || `${usedHandle != null ? usedHandle : `handle`}-next`);
|
|
1399
|
+
return this.fetchSnapshot(opts);
|
|
1400
|
+
}
|
|
1401
|
+
throw e;
|
|
1402
|
+
}
|
|
1369
1403
|
if (!response.ok) {
|
|
1370
|
-
throw
|
|
1371
|
-
response.status,
|
|
1372
|
-
void 0,
|
|
1373
|
-
void 0,
|
|
1374
|
-
Object.fromEntries([...response.headers.entries()]),
|
|
1375
|
-
fetchUrl.toString()
|
|
1376
|
-
);
|
|
1404
|
+
throw await FetchError.fromResponse(response, fetchUrl.toString());
|
|
1377
1405
|
}
|
|
1378
|
-
const schema = (
|
|
1406
|
+
const schema = (_c = __privateGet(this, _schema)) != null ? _c : getSchemaFromHeaders(response.headers, {
|
|
1379
1407
|
required: true,
|
|
1380
1408
|
url: fetchUrl.toString()
|
|
1381
1409
|
});
|
|
@@ -1384,10 +1412,7 @@ var ShapeStream = class {
|
|
|
1384
1412
|
rawData,
|
|
1385
1413
|
schema
|
|
1386
1414
|
);
|
|
1387
|
-
return {
|
|
1388
|
-
metadata,
|
|
1389
|
-
data
|
|
1390
|
-
};
|
|
1415
|
+
return { metadata, data };
|
|
1391
1416
|
}
|
|
1392
1417
|
};
|
|
1393
1418
|
_error = new WeakMap();
|
|
@@ -1703,8 +1728,9 @@ onInitialResponse_fn = async function(response) {
|
|
|
1703
1728
|
);
|
|
1704
1729
|
} else {
|
|
1705
1730
|
console.warn(
|
|
1706
|
-
`[Electric] Received stale cached response with expired shape handle. This should not happen and indicates a proxy/CDN caching misconfiguration. The response contained handle "${shapeHandle}" which was previously marked as expired. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. Ignoring the stale
|
|
1731
|
+
`[Electric] Received stale cached response with expired shape handle. This should not happen and indicates a proxy/CDN caching misconfiguration. The response contained handle "${shapeHandle}" which was previously marked as expired. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. Ignoring the stale response and continuing with handle "${__privateGet(this, _shapeHandle)}".`
|
|
1707
1732
|
);
|
|
1733
|
+
return;
|
|
1708
1734
|
}
|
|
1709
1735
|
}
|
|
1710
1736
|
const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
|
|
@@ -1957,6 +1983,44 @@ reset_fn = function(handle) {
|
|
|
1957
1983
|
__privateSet(this, _staleCacheBuster, void 0);
|
|
1958
1984
|
__privateSet(this, _staleCacheRetryCount, 0);
|
|
1959
1985
|
};
|
|
1986
|
+
buildSubsetBody_fn = function(opts) {
|
|
1987
|
+
var _a, _b, _c, _d;
|
|
1988
|
+
const body = {};
|
|
1989
|
+
if (opts.whereExpr) {
|
|
1990
|
+
body.where = compileExpression(
|
|
1991
|
+
opts.whereExpr,
|
|
1992
|
+
(_a = this.options.columnMapper) == null ? void 0 : _a.encode
|
|
1993
|
+
);
|
|
1994
|
+
body.where_expr = opts.whereExpr;
|
|
1995
|
+
} else if (opts.where && typeof opts.where === `string`) {
|
|
1996
|
+
body.where = encodeWhereClause(
|
|
1997
|
+
opts.where,
|
|
1998
|
+
(_b = this.options.columnMapper) == null ? void 0 : _b.encode
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
if (opts.params) {
|
|
2002
|
+
body.params = opts.params;
|
|
2003
|
+
}
|
|
2004
|
+
if (opts.limit !== void 0) {
|
|
2005
|
+
body.limit = opts.limit;
|
|
2006
|
+
}
|
|
2007
|
+
if (opts.offset !== void 0) {
|
|
2008
|
+
body.offset = opts.offset;
|
|
2009
|
+
}
|
|
2010
|
+
if (opts.orderByExpr) {
|
|
2011
|
+
body.order_by = compileOrderBy(
|
|
2012
|
+
opts.orderByExpr,
|
|
2013
|
+
(_c = this.options.columnMapper) == null ? void 0 : _c.encode
|
|
2014
|
+
);
|
|
2015
|
+
body.order_by_expr = opts.orderByExpr;
|
|
2016
|
+
} else if (opts.orderBy && typeof opts.orderBy === `string`) {
|
|
2017
|
+
body.order_by = encodeWhereClause(
|
|
2018
|
+
opts.orderBy,
|
|
2019
|
+
(_d = this.options.columnMapper) == null ? void 0 : _d.encode
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
return body;
|
|
2023
|
+
};
|
|
1960
2024
|
ShapeStream.Replica = {
|
|
1961
2025
|
FULL: `full`,
|
|
1962
2026
|
DEFAULT: `default`
|