@electric-sql/client 1.3.0 → 1.4.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 +125 -7
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +65 -1
- package/dist/index.browser.mjs +3 -3
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.legacy-esm.js +123 -7
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +123 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +59 -3
- package/src/constants.ts +4 -0
- package/src/expression-compiler.ts +132 -0
- package/src/fetch.ts +16 -0
- package/src/index.ts +1 -0
- package/src/types.ts +28 -0
package/dist/cjs/index.cjs
CHANGED
|
@@ -71,6 +71,8 @@ __export(src_exports, {
|
|
|
71
71
|
Shape: () => Shape,
|
|
72
72
|
ShapeStream: () => ShapeStream,
|
|
73
73
|
camelToSnake: () => camelToSnake,
|
|
74
|
+
compileExpression: () => compileExpression,
|
|
75
|
+
compileOrderBy: () => compileOrderBy,
|
|
74
76
|
createColumnMapper: () => createColumnMapper,
|
|
75
77
|
isChangeMessage: () => isChangeMessage,
|
|
76
78
|
isControlMessage: () => isControlMessage,
|
|
@@ -502,6 +504,8 @@ var SUBSET_PARAM_LIMIT = `subset__limit`;
|
|
|
502
504
|
var SUBSET_PARAM_OFFSET = `subset__offset`;
|
|
503
505
|
var SUBSET_PARAM_ORDER_BY = `subset__order_by`;
|
|
504
506
|
var SUBSET_PARAM_WHERE_PARAMS = `subset__params`;
|
|
507
|
+
var SUBSET_PARAM_WHERE_EXPR = `subset__where_expr`;
|
|
508
|
+
var SUBSET_PARAM_ORDER_BY_EXPR = `subset__order_by_expr`;
|
|
505
509
|
var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
506
510
|
LIVE_QUERY_PARAM,
|
|
507
511
|
LIVE_SSE_QUERY_PARAM,
|
|
@@ -514,7 +518,9 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
|
514
518
|
SUBSET_PARAM_LIMIT,
|
|
515
519
|
SUBSET_PARAM_OFFSET,
|
|
516
520
|
SUBSET_PARAM_ORDER_BY,
|
|
517
|
-
SUBSET_PARAM_WHERE_PARAMS
|
|
521
|
+
SUBSET_PARAM_WHERE_PARAMS,
|
|
522
|
+
SUBSET_PARAM_WHERE_EXPR,
|
|
523
|
+
SUBSET_PARAM_ORDER_BY_EXPR
|
|
518
524
|
];
|
|
519
525
|
|
|
520
526
|
// src/fetch.ts
|
|
@@ -768,6 +774,13 @@ function getNextChunkUrl(url, res) {
|
|
|
768
774
|
if (!shapeHandle || !lastOffset || isUpToDate) return;
|
|
769
775
|
const nextUrl = new URL(url);
|
|
770
776
|
if (nextUrl.searchParams.has(LIVE_QUERY_PARAM)) return;
|
|
777
|
+
const expiredHandle = nextUrl.searchParams.get(EXPIRED_HANDLE_QUERY_PARAM);
|
|
778
|
+
if (expiredHandle && shapeHandle === expiredHandle) {
|
|
779
|
+
console.warn(
|
|
780
|
+
`[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. Skipping prefetch to prevent infinite 409 loop.`
|
|
781
|
+
);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
771
784
|
nextUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, shapeHandle);
|
|
772
785
|
nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset);
|
|
773
786
|
nextUrl.searchParams.sort();
|
|
@@ -794,6 +807,81 @@ function chainAborter(aborter, sourceSignal) {
|
|
|
794
807
|
function noop() {
|
|
795
808
|
}
|
|
796
809
|
|
|
810
|
+
// src/expression-compiler.ts
|
|
811
|
+
function compileExpression(expr, columnMapper) {
|
|
812
|
+
switch (expr.type) {
|
|
813
|
+
case `ref`: {
|
|
814
|
+
const mappedColumn = columnMapper ? columnMapper(expr.column) : expr.column;
|
|
815
|
+
return quoteIdentifier(mappedColumn);
|
|
816
|
+
}
|
|
817
|
+
case `val`:
|
|
818
|
+
return `$${expr.paramIndex}`;
|
|
819
|
+
case `func`:
|
|
820
|
+
return compileFunction(expr, columnMapper);
|
|
821
|
+
default: {
|
|
822
|
+
const _exhaustive = expr;
|
|
823
|
+
throw new Error(`Unknown expression type: ${JSON.stringify(_exhaustive)}`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
function compileFunction(expr, columnMapper) {
|
|
828
|
+
const args = expr.args.map((arg) => compileExpression(arg, columnMapper));
|
|
829
|
+
switch (expr.name) {
|
|
830
|
+
// Binary comparison operators
|
|
831
|
+
case `eq`:
|
|
832
|
+
return `${args[0]} = ${args[1]}`;
|
|
833
|
+
case `gt`:
|
|
834
|
+
return `${args[0]} > ${args[1]}`;
|
|
835
|
+
case `gte`:
|
|
836
|
+
return `${args[0]} >= ${args[1]}`;
|
|
837
|
+
case `lt`:
|
|
838
|
+
return `${args[0]} < ${args[1]}`;
|
|
839
|
+
case `lte`:
|
|
840
|
+
return `${args[0]} <= ${args[1]}`;
|
|
841
|
+
// Logical operators
|
|
842
|
+
case `and`:
|
|
843
|
+
return args.map((a) => `(${a})`).join(` AND `);
|
|
844
|
+
case `or`:
|
|
845
|
+
return args.map((a) => `(${a})`).join(` OR `);
|
|
846
|
+
case `not`:
|
|
847
|
+
return `NOT (${args[0]})`;
|
|
848
|
+
// Special operators
|
|
849
|
+
case `in`:
|
|
850
|
+
return `${args[0]} = ANY(${args[1]})`;
|
|
851
|
+
case `like`:
|
|
852
|
+
return `${args[0]} LIKE ${args[1]}`;
|
|
853
|
+
case `ilike`:
|
|
854
|
+
return `${args[0]} ILIKE ${args[1]}`;
|
|
855
|
+
case `isNull`:
|
|
856
|
+
case `isUndefined`:
|
|
857
|
+
return `${args[0]} IS NULL`;
|
|
858
|
+
// String functions
|
|
859
|
+
case `upper`:
|
|
860
|
+
return `UPPER(${args[0]})`;
|
|
861
|
+
case `lower`:
|
|
862
|
+
return `LOWER(${args[0]})`;
|
|
863
|
+
case `length`:
|
|
864
|
+
return `LENGTH(${args[0]})`;
|
|
865
|
+
case `concat`:
|
|
866
|
+
return `CONCAT(${args.join(`, `)})`;
|
|
867
|
+
// Other functions
|
|
868
|
+
case `coalesce`:
|
|
869
|
+
return `COALESCE(${args.join(`, `)})`;
|
|
870
|
+
default:
|
|
871
|
+
throw new Error(`Unknown function: ${expr.name}`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
function compileOrderBy(clauses, columnMapper) {
|
|
875
|
+
return clauses.map((clause) => {
|
|
876
|
+
const mappedColumn = columnMapper ? columnMapper(clause.column) : clause.column;
|
|
877
|
+
let sql = quoteIdentifier(mappedColumn);
|
|
878
|
+
if (clause.direction === `desc`) sql += ` DESC`;
|
|
879
|
+
if (clause.nulls === `first`) sql += ` NULLS FIRST`;
|
|
880
|
+
if (clause.nulls === `last`) sql += ` NULLS LAST`;
|
|
881
|
+
return sql;
|
|
882
|
+
}).join(`, `);
|
|
883
|
+
}
|
|
884
|
+
|
|
797
885
|
// src/client.ts
|
|
798
886
|
var import_fetch_event_source = require("@microsoft/fetch-event-source");
|
|
799
887
|
|
|
@@ -1459,7 +1547,7 @@ requestShape_fn = async function() {
|
|
|
1459
1547
|
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
1460
1548
|
};
|
|
1461
1549
|
constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
1462
|
-
var _a, _b, _c, _d;
|
|
1550
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1463
1551
|
const [requestHeaders, params] = await Promise.all([
|
|
1464
1552
|
resolveHeaders(this.options.headers),
|
|
1465
1553
|
this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
|
|
@@ -1504,10 +1592,20 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
|
1504
1592
|
}
|
|
1505
1593
|
}
|
|
1506
1594
|
if (subsetParams) {
|
|
1507
|
-
if (subsetParams.
|
|
1595
|
+
if (subsetParams.whereExpr) {
|
|
1596
|
+
const compiledWhere = compileExpression(
|
|
1597
|
+
subsetParams.whereExpr,
|
|
1598
|
+
(_c = this.options.columnMapper) == null ? void 0 : _c.encode
|
|
1599
|
+
);
|
|
1600
|
+
setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, compiledWhere);
|
|
1601
|
+
fetchUrl.searchParams.set(
|
|
1602
|
+
SUBSET_PARAM_WHERE_EXPR,
|
|
1603
|
+
JSON.stringify(subsetParams.whereExpr)
|
|
1604
|
+
);
|
|
1605
|
+
} else if (subsetParams.where && typeof subsetParams.where === `string`) {
|
|
1508
1606
|
const encodedWhere = encodeWhereClause(
|
|
1509
1607
|
subsetParams.where,
|
|
1510
|
-
(
|
|
1608
|
+
(_d = this.options.columnMapper) == null ? void 0 : _d.encode
|
|
1511
1609
|
);
|
|
1512
1610
|
setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, encodedWhere);
|
|
1513
1611
|
}
|
|
@@ -1520,10 +1618,20 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
|
1520
1618
|
setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit);
|
|
1521
1619
|
if (subsetParams.offset)
|
|
1522
1620
|
setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset);
|
|
1523
|
-
if (subsetParams.
|
|
1621
|
+
if (subsetParams.orderByExpr) {
|
|
1622
|
+
const compiledOrderBy = compileOrderBy(
|
|
1623
|
+
subsetParams.orderByExpr,
|
|
1624
|
+
(_e = this.options.columnMapper) == null ? void 0 : _e.encode
|
|
1625
|
+
);
|
|
1626
|
+
setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, compiledOrderBy);
|
|
1627
|
+
fetchUrl.searchParams.set(
|
|
1628
|
+
SUBSET_PARAM_ORDER_BY_EXPR,
|
|
1629
|
+
JSON.stringify(subsetParams.orderByExpr)
|
|
1630
|
+
);
|
|
1631
|
+
} else if (subsetParams.orderBy && typeof subsetParams.orderBy === `string`) {
|
|
1524
1632
|
const encodedOrderBy = encodeWhereClause(
|
|
1525
1633
|
subsetParams.orderBy,
|
|
1526
|
-
(
|
|
1634
|
+
(_f = this.options.columnMapper) == null ? void 0 : _f.encode
|
|
1527
1635
|
);
|
|
1528
1636
|
setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, encodedOrderBy);
|
|
1529
1637
|
}
|
|
@@ -1574,7 +1682,15 @@ onInitialResponse_fn = async function(response) {
|
|
|
1574
1682
|
const { headers, status } = response;
|
|
1575
1683
|
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
|
|
1576
1684
|
if (shapeHandle) {
|
|
1577
|
-
|
|
1685
|
+
const shapeKey = __privateGet(this, _currentFetchUrl) ? canonicalShapeKey(__privateGet(this, _currentFetchUrl)) : null;
|
|
1686
|
+
const expiredHandle = shapeKey ? expiredShapesCache.getExpiredHandle(shapeKey) : null;
|
|
1687
|
+
if (shapeHandle !== expiredHandle) {
|
|
1688
|
+
__privateSet(this, _shapeHandle, shapeHandle);
|
|
1689
|
+
} else {
|
|
1690
|
+
console.warn(
|
|
1691
|
+
`[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 handle and continuing with handle "${__privateGet(this, _shapeHandle)}".`
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1578
1694
|
}
|
|
1579
1695
|
const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
|
|
1580
1696
|
if (lastOffset) {
|
|
@@ -2097,6 +2213,8 @@ notify_fn = function() {
|
|
|
2097
2213
|
Shape,
|
|
2098
2214
|
ShapeStream,
|
|
2099
2215
|
camelToSnake,
|
|
2216
|
+
compileExpression,
|
|
2217
|
+
compileOrderBy,
|
|
2100
2218
|
createColumnMapper,
|
|
2101
2219
|
isChangeMessage,
|
|
2102
2220
|
isControlMessage,
|