@electric-sql/client 1.1.4 → 1.2.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 +426 -50
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +229 -9
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +229 -9
- package/dist/index.legacy-esm.js +419 -47
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +421 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +254 -42
- package/src/column-mapper.ts +357 -0
- package/src/index.ts +7 -0
- package/src/parser.ts +45 -7
- package/src/up-to-date-tracker.ts +157 -0
package/dist/index.legacy-esm.js
CHANGED
|
@@ -203,15 +203,37 @@ var MessageParser = class {
|
|
|
203
203
|
parse(messages, schema) {
|
|
204
204
|
return JSON.parse(messages, (key, value) => {
|
|
205
205
|
if ((key === `value` || key === `old_value`) && typeof value === `object` && value !== null) {
|
|
206
|
-
|
|
207
|
-
Object.keys(row).forEach((key2) => {
|
|
208
|
-
row[key2] = this.parseRow(key2, row[key2], schema);
|
|
209
|
-
});
|
|
210
|
-
if (this.transformer) value = this.transformer(value);
|
|
206
|
+
return this.transformMessageValue(value, schema);
|
|
211
207
|
}
|
|
212
208
|
return value;
|
|
213
209
|
});
|
|
214
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Parse an array of ChangeMessages from a snapshot response.
|
|
213
|
+
* Applies type parsing and transformations to the value and old_value properties.
|
|
214
|
+
*/
|
|
215
|
+
parseSnapshotData(messages, schema) {
|
|
216
|
+
return messages.map((message) => {
|
|
217
|
+
const msg = message;
|
|
218
|
+
if (msg.value && typeof msg.value === `object` && msg.value !== null) {
|
|
219
|
+
msg.value = this.transformMessageValue(msg.value, schema);
|
|
220
|
+
}
|
|
221
|
+
if (msg.old_value && typeof msg.old_value === `object` && msg.old_value !== null) {
|
|
222
|
+
msg.old_value = this.transformMessageValue(msg.old_value, schema);
|
|
223
|
+
}
|
|
224
|
+
return msg;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Transform a message value or old_value object by parsing its columns.
|
|
229
|
+
*/
|
|
230
|
+
transformMessageValue(value, schema) {
|
|
231
|
+
const row = value;
|
|
232
|
+
Object.keys(row).forEach((key) => {
|
|
233
|
+
row[key] = this.parseRow(key, row[key], schema);
|
|
234
|
+
});
|
|
235
|
+
return this.transformer ? this.transformer(row) : row;
|
|
236
|
+
}
|
|
215
237
|
// Parses the message values using the provided parser based on the schema information
|
|
216
238
|
parseRow(key, value, schema) {
|
|
217
239
|
var _b;
|
|
@@ -247,6 +269,151 @@ function makeNullableParser(parser, columnInfo, columnName) {
|
|
|
247
269
|
};
|
|
248
270
|
}
|
|
249
271
|
|
|
272
|
+
// src/column-mapper.ts
|
|
273
|
+
function snakeToCamel(str) {
|
|
274
|
+
var _a, _b, _c, _d;
|
|
275
|
+
const leadingUnderscores = (_b = (_a = str.match(/^_+/)) == null ? void 0 : _a[0]) != null ? _b : ``;
|
|
276
|
+
const withoutLeading = str.slice(leadingUnderscores.length);
|
|
277
|
+
const trailingUnderscores = (_d = (_c = withoutLeading.match(/_+$/)) == null ? void 0 : _c[0]) != null ? _d : ``;
|
|
278
|
+
const core = trailingUnderscores ? withoutLeading.slice(
|
|
279
|
+
0,
|
|
280
|
+
withoutLeading.length - trailingUnderscores.length
|
|
281
|
+
) : withoutLeading;
|
|
282
|
+
const normalized = core.toLowerCase();
|
|
283
|
+
const camelCased = normalized.replace(
|
|
284
|
+
/_+([a-z])/g,
|
|
285
|
+
(_, letter) => letter.toUpperCase()
|
|
286
|
+
);
|
|
287
|
+
return leadingUnderscores + camelCased + trailingUnderscores;
|
|
288
|
+
}
|
|
289
|
+
function camelToSnake(str) {
|
|
290
|
+
return str.replace(/([a-z])([A-Z])/g, `$1_$2`).replace(/([A-Z]+)([A-Z][a-z])/g, `$1_$2`).toLowerCase();
|
|
291
|
+
}
|
|
292
|
+
function createColumnMapper(mapping) {
|
|
293
|
+
const reverseMapping = {};
|
|
294
|
+
for (const [dbName, appName] of Object.entries(mapping)) {
|
|
295
|
+
reverseMapping[appName] = dbName;
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
decode: (dbColumnName) => {
|
|
299
|
+
var _a;
|
|
300
|
+
return (_a = mapping[dbColumnName]) != null ? _a : dbColumnName;
|
|
301
|
+
},
|
|
302
|
+
encode: (appColumnName) => {
|
|
303
|
+
var _a;
|
|
304
|
+
return (_a = reverseMapping[appColumnName]) != null ? _a : appColumnName;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function encodeWhereClause(whereClause, encode) {
|
|
309
|
+
if (!whereClause || !encode) return whereClause != null ? whereClause : ``;
|
|
310
|
+
const sqlKeywords = /* @__PURE__ */ new Set([
|
|
311
|
+
`SELECT`,
|
|
312
|
+
`FROM`,
|
|
313
|
+
`WHERE`,
|
|
314
|
+
`AND`,
|
|
315
|
+
`OR`,
|
|
316
|
+
`NOT`,
|
|
317
|
+
`IN`,
|
|
318
|
+
`IS`,
|
|
319
|
+
`NULL`,
|
|
320
|
+
`NULLS`,
|
|
321
|
+
`FIRST`,
|
|
322
|
+
`LAST`,
|
|
323
|
+
`TRUE`,
|
|
324
|
+
`FALSE`,
|
|
325
|
+
`LIKE`,
|
|
326
|
+
`ILIKE`,
|
|
327
|
+
`BETWEEN`,
|
|
328
|
+
`ASC`,
|
|
329
|
+
`DESC`,
|
|
330
|
+
`LIMIT`,
|
|
331
|
+
`OFFSET`,
|
|
332
|
+
`ORDER`,
|
|
333
|
+
`BY`,
|
|
334
|
+
`GROUP`,
|
|
335
|
+
`HAVING`,
|
|
336
|
+
`DISTINCT`,
|
|
337
|
+
`AS`,
|
|
338
|
+
`ON`,
|
|
339
|
+
`JOIN`,
|
|
340
|
+
`LEFT`,
|
|
341
|
+
`RIGHT`,
|
|
342
|
+
`INNER`,
|
|
343
|
+
`OUTER`,
|
|
344
|
+
`CROSS`,
|
|
345
|
+
`CASE`,
|
|
346
|
+
`WHEN`,
|
|
347
|
+
`THEN`,
|
|
348
|
+
`ELSE`,
|
|
349
|
+
`END`,
|
|
350
|
+
`CAST`,
|
|
351
|
+
`LOWER`,
|
|
352
|
+
`UPPER`,
|
|
353
|
+
`COALESCE`,
|
|
354
|
+
`NULLIF`
|
|
355
|
+
]);
|
|
356
|
+
const quotedRanges = [];
|
|
357
|
+
let pos = 0;
|
|
358
|
+
while (pos < whereClause.length) {
|
|
359
|
+
const ch = whereClause[pos];
|
|
360
|
+
if (ch === `'` || ch === `"`) {
|
|
361
|
+
const start = pos;
|
|
362
|
+
const quoteChar = ch;
|
|
363
|
+
pos++;
|
|
364
|
+
while (pos < whereClause.length) {
|
|
365
|
+
if (whereClause[pos] === quoteChar) {
|
|
366
|
+
if (whereClause[pos + 1] === quoteChar) {
|
|
367
|
+
pos += 2;
|
|
368
|
+
} else {
|
|
369
|
+
pos++;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
pos++;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
quotedRanges.push({ start, end: pos });
|
|
377
|
+
} else {
|
|
378
|
+
pos++;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const isInQuotedString = (pos2) => {
|
|
382
|
+
return quotedRanges.some((range) => pos2 >= range.start && pos2 < range.end);
|
|
383
|
+
};
|
|
384
|
+
const identifierPattern = new RegExp("(?<![a-zA-Z0-9_])([a-zA-Z_][a-zA-Z0-9_]*)(?![a-zA-Z0-9_])", "g");
|
|
385
|
+
return whereClause.replace(identifierPattern, (match, _p1, offset) => {
|
|
386
|
+
if (isInQuotedString(offset)) {
|
|
387
|
+
return match;
|
|
388
|
+
}
|
|
389
|
+
if (sqlKeywords.has(match.toUpperCase())) {
|
|
390
|
+
return match;
|
|
391
|
+
}
|
|
392
|
+
if (match.startsWith(`$`)) {
|
|
393
|
+
return match;
|
|
394
|
+
}
|
|
395
|
+
const encoded = encode(match);
|
|
396
|
+
return encoded;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function snakeCamelMapper(schema) {
|
|
400
|
+
if (schema) {
|
|
401
|
+
const mapping = {};
|
|
402
|
+
for (const dbColumn of Object.keys(schema)) {
|
|
403
|
+
mapping[dbColumn] = snakeToCamel(dbColumn);
|
|
404
|
+
}
|
|
405
|
+
return createColumnMapper(mapping);
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
decode: (dbColumnName) => {
|
|
409
|
+
return snakeToCamel(dbColumnName);
|
|
410
|
+
},
|
|
411
|
+
encode: (appColumnName) => {
|
|
412
|
+
return camelToSnake(appColumnName);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
250
417
|
// src/helpers.ts
|
|
251
418
|
function isChangeMessage(message) {
|
|
252
419
|
return `key` in message;
|
|
@@ -642,6 +809,127 @@ var ExpiredShapesCache = class {
|
|
|
642
809
|
};
|
|
643
810
|
var expiredShapesCache = new ExpiredShapesCache();
|
|
644
811
|
|
|
812
|
+
// src/up-to-date-tracker.ts
|
|
813
|
+
var UpToDateTracker = class {
|
|
814
|
+
constructor() {
|
|
815
|
+
this.data = {};
|
|
816
|
+
this.storageKey = `electric_up_to_date_tracker`;
|
|
817
|
+
this.cacheTTL = 6e4;
|
|
818
|
+
// 60s to match typical CDN s-maxage cache duration
|
|
819
|
+
this.maxEntries = 250;
|
|
820
|
+
this.writeThrottleMs = 6e4;
|
|
821
|
+
// Throttle localStorage writes to once per 60s
|
|
822
|
+
this.lastWriteTime = 0;
|
|
823
|
+
this.load();
|
|
824
|
+
this.cleanup();
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Records that a shape received an up-to-date message with a specific cursor.
|
|
828
|
+
* This timestamp and cursor are used to detect cache replay scenarios.
|
|
829
|
+
* Updates in-memory immediately, but throttles localStorage writes.
|
|
830
|
+
*/
|
|
831
|
+
recordUpToDate(shapeKey, cursor) {
|
|
832
|
+
this.data[shapeKey] = {
|
|
833
|
+
timestamp: Date.now(),
|
|
834
|
+
cursor
|
|
835
|
+
};
|
|
836
|
+
const keys = Object.keys(this.data);
|
|
837
|
+
if (keys.length > this.maxEntries) {
|
|
838
|
+
const oldest = keys.reduce(
|
|
839
|
+
(min, k) => this.data[k].timestamp < this.data[min].timestamp ? k : min
|
|
840
|
+
);
|
|
841
|
+
delete this.data[oldest];
|
|
842
|
+
}
|
|
843
|
+
this.scheduleSave();
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Schedules a throttled save to localStorage.
|
|
847
|
+
* Writes immediately if enough time has passed, otherwise schedules for later.
|
|
848
|
+
*/
|
|
849
|
+
scheduleSave() {
|
|
850
|
+
const now = Date.now();
|
|
851
|
+
const timeSinceLastWrite = now - this.lastWriteTime;
|
|
852
|
+
if (timeSinceLastWrite >= this.writeThrottleMs) {
|
|
853
|
+
this.lastWriteTime = now;
|
|
854
|
+
this.save();
|
|
855
|
+
} else if (!this.pendingSaveTimer) {
|
|
856
|
+
const delay = this.writeThrottleMs - timeSinceLastWrite;
|
|
857
|
+
this.pendingSaveTimer = setTimeout(() => {
|
|
858
|
+
this.lastWriteTime = Date.now();
|
|
859
|
+
this.pendingSaveTimer = void 0;
|
|
860
|
+
this.save();
|
|
861
|
+
}, delay);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Checks if we should enter replay mode for this shape.
|
|
866
|
+
* Returns the last seen cursor if there's a recent up-to-date (< 60s),
|
|
867
|
+
* which means we'll likely be replaying cached responses.
|
|
868
|
+
* Returns null if no recent up-to-date exists.
|
|
869
|
+
*/
|
|
870
|
+
shouldEnterReplayMode(shapeKey) {
|
|
871
|
+
const entry = this.data[shapeKey];
|
|
872
|
+
if (!entry) {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
const age = Date.now() - entry.timestamp;
|
|
876
|
+
if (age >= this.cacheTTL) {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
return entry.cursor;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Cleans up expired entries from the cache.
|
|
883
|
+
* Called on initialization and can be called periodically.
|
|
884
|
+
*/
|
|
885
|
+
cleanup() {
|
|
886
|
+
const now = Date.now();
|
|
887
|
+
const keys = Object.keys(this.data);
|
|
888
|
+
let modified = false;
|
|
889
|
+
for (const key of keys) {
|
|
890
|
+
const age = now - this.data[key].timestamp;
|
|
891
|
+
if (age > this.cacheTTL) {
|
|
892
|
+
delete this.data[key];
|
|
893
|
+
modified = true;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (modified) {
|
|
897
|
+
this.save();
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
save() {
|
|
901
|
+
if (typeof localStorage === `undefined`) return;
|
|
902
|
+
try {
|
|
903
|
+
localStorage.setItem(this.storageKey, JSON.stringify(this.data));
|
|
904
|
+
} catch (e) {
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
load() {
|
|
908
|
+
if (typeof localStorage === `undefined`) return;
|
|
909
|
+
try {
|
|
910
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
911
|
+
if (stored) {
|
|
912
|
+
this.data = JSON.parse(stored);
|
|
913
|
+
}
|
|
914
|
+
} catch (e) {
|
|
915
|
+
this.data = {};
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Clears all tracked up-to-date timestamps.
|
|
920
|
+
* Useful for testing or manual cache invalidation.
|
|
921
|
+
*/
|
|
922
|
+
clear() {
|
|
923
|
+
this.data = {};
|
|
924
|
+
if (this.pendingSaveTimer) {
|
|
925
|
+
clearTimeout(this.pendingSaveTimer);
|
|
926
|
+
this.pendingSaveTimer = void 0;
|
|
927
|
+
}
|
|
928
|
+
this.save();
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
var upToDateTracker = new UpToDateTracker();
|
|
932
|
+
|
|
645
933
|
// src/snapshot-tracker.ts
|
|
646
934
|
var SnapshotTracker = class {
|
|
647
935
|
constructor() {
|
|
@@ -751,7 +1039,7 @@ function canonicalShapeKey(url) {
|
|
|
751
1039
|
cleanUrl.searchParams.sort();
|
|
752
1040
|
return cleanUrl.toString();
|
|
753
1041
|
}
|
|
754
|
-
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, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _ShapeStream_instances, 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
|
|
1042
|
+
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, _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;
|
|
755
1043
|
var ShapeStream = class {
|
|
756
1044
|
constructor(options) {
|
|
757
1045
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -786,6 +1074,10 @@ var ShapeStream = class {
|
|
|
786
1074
|
// counter for concurrent snapshot requests
|
|
787
1075
|
__privateAdd(this, _midStreamPromise);
|
|
788
1076
|
__privateAdd(this, _midStreamPromiseResolver);
|
|
1077
|
+
__privateAdd(this, _lastSeenCursor);
|
|
1078
|
+
// Last seen cursor from previous session (used to detect cached responses)
|
|
1079
|
+
__privateAdd(this, _currentFetchUrl);
|
|
1080
|
+
// Current fetch URL for computing shape key
|
|
789
1081
|
__privateAdd(this, _lastSseConnectionStartTime);
|
|
790
1082
|
__privateAdd(this, _minSseConnectionDuration, 1e3);
|
|
791
1083
|
// Minimum expected SSE connection duration (1 second)
|
|
@@ -804,10 +1096,21 @@ var ShapeStream = class {
|
|
|
804
1096
|
__privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
|
|
805
1097
|
__privateSet(this, _liveCacheBuster, ``);
|
|
806
1098
|
__privateSet(this, _shapeHandle, this.options.handle);
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1099
|
+
let transformer;
|
|
1100
|
+
if (options.columnMapper) {
|
|
1101
|
+
const applyColumnMapper = (row) => {
|
|
1102
|
+
const result = {};
|
|
1103
|
+
for (const [dbKey, value] of Object.entries(row)) {
|
|
1104
|
+
const appKey = options.columnMapper.decode(dbKey);
|
|
1105
|
+
result[appKey] = value;
|
|
1106
|
+
}
|
|
1107
|
+
return result;
|
|
1108
|
+
};
|
|
1109
|
+
transformer = options.transformer ? (row) => options.transformer(applyColumnMapper(row)) : applyColumnMapper;
|
|
1110
|
+
} else {
|
|
1111
|
+
transformer = options.transformer;
|
|
1112
|
+
}
|
|
1113
|
+
__privateSet(this, _messageParser, new MessageParser(options.parser, transformer));
|
|
811
1114
|
__privateSet(this, _onError, this.options.onError);
|
|
812
1115
|
__privateSet(this, _mode, (_b = this.options.log) != null ? _b : `full`);
|
|
813
1116
|
const baseFetchClient = (_c = options.fetchClient) != null ? _c : (...args) => fetch(...args);
|
|
@@ -896,7 +1199,7 @@ var ShapeStream = class {
|
|
|
896
1199
|
__privateSet(this, _isRefreshing, false);
|
|
897
1200
|
}
|
|
898
1201
|
/**
|
|
899
|
-
* Request a snapshot for subset of data.
|
|
1202
|
+
* Request a snapshot for subset of data and inject it into the subscribed data stream.
|
|
900
1203
|
*
|
|
901
1204
|
* Only available when mode is `changes_only`.
|
|
902
1205
|
* Returns the insertion point & the data, but more importantly injects the data
|
|
@@ -922,8 +1225,7 @@ var ShapeStream = class {
|
|
|
922
1225
|
if (__privateGet(this, _activeSnapshotRequests) === 1) {
|
|
923
1226
|
__privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
|
|
924
1227
|
}
|
|
925
|
-
const {
|
|
926
|
-
const { metadata, data } = await __privateMethod(this, _ShapeStream_instances, fetchSnapshot_fn).call(this, fetchUrl, requestHeaders);
|
|
1228
|
+
const { metadata, data } = await this.fetchSnapshot(opts);
|
|
927
1229
|
const dataWithEndBoundary = data.concat([
|
|
928
1230
|
{ headers: __spreadValues({ control: `snapshot-end` }, metadata) }
|
|
929
1231
|
]);
|
|
@@ -943,6 +1245,42 @@ var ShapeStream = class {
|
|
|
943
1245
|
}
|
|
944
1246
|
}
|
|
945
1247
|
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Fetch a snapshot for subset of data.
|
|
1250
|
+
* Returns the metadata and the data, but does not inject it into the subscribed data stream.
|
|
1251
|
+
*
|
|
1252
|
+
* @param opts - The options for the snapshot request.
|
|
1253
|
+
* @returns The metadata and the data for the snapshot.
|
|
1254
|
+
*/
|
|
1255
|
+
async fetchSnapshot(opts) {
|
|
1256
|
+
var _a;
|
|
1257
|
+
const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
|
|
1258
|
+
const response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), {
|
|
1259
|
+
headers: requestHeaders
|
|
1260
|
+
});
|
|
1261
|
+
if (!response.ok) {
|
|
1262
|
+
throw new FetchError(
|
|
1263
|
+
response.status,
|
|
1264
|
+
void 0,
|
|
1265
|
+
void 0,
|
|
1266
|
+
Object.fromEntries([...response.headers.entries()]),
|
|
1267
|
+
fetchUrl.toString()
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
const schema = (_a = __privateGet(this, _schema)) != null ? _a : getSchemaFromHeaders(response.headers, {
|
|
1271
|
+
required: true,
|
|
1272
|
+
url: fetchUrl.toString()
|
|
1273
|
+
});
|
|
1274
|
+
const { metadata, data: rawData } = await response.json();
|
|
1275
|
+
const data = __privateGet(this, _messageParser).parseSnapshotData(
|
|
1276
|
+
rawData,
|
|
1277
|
+
schema
|
|
1278
|
+
);
|
|
1279
|
+
return {
|
|
1280
|
+
metadata,
|
|
1281
|
+
data
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
946
1284
|
};
|
|
947
1285
|
_error = new WeakMap();
|
|
948
1286
|
_fetchClient2 = new WeakMap();
|
|
@@ -971,6 +1309,8 @@ _snapshotTracker = new WeakMap();
|
|
|
971
1309
|
_activeSnapshotRequests = new WeakMap();
|
|
972
1310
|
_midStreamPromise = new WeakMap();
|
|
973
1311
|
_midStreamPromiseResolver = new WeakMap();
|
|
1312
|
+
_lastSeenCursor = new WeakMap();
|
|
1313
|
+
_currentFetchUrl = new WeakMap();
|
|
974
1314
|
_lastSseConnectionStartTime = new WeakMap();
|
|
975
1315
|
_minSseConnectionDuration = new WeakMap();
|
|
976
1316
|
_consecutiveShortSseConnections = new WeakMap();
|
|
@@ -980,6 +1320,9 @@ _sseBackoffBaseDelay = new WeakMap();
|
|
|
980
1320
|
_sseBackoffMaxDelay = new WeakMap();
|
|
981
1321
|
_unsubscribeFromVisibilityChanges = new WeakMap();
|
|
982
1322
|
_ShapeStream_instances = new WeakSet();
|
|
1323
|
+
replayMode_get = function() {
|
|
1324
|
+
return __privateGet(this, _lastSeenCursor) !== void 0;
|
|
1325
|
+
};
|
|
983
1326
|
start_fn = async function() {
|
|
984
1327
|
var _a, _b, _c, _d, _e;
|
|
985
1328
|
__privateSet(this, _started, true);
|
|
@@ -1074,6 +1417,7 @@ requestShape_fn = async function() {
|
|
|
1074
1417
|
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
1075
1418
|
};
|
|
1076
1419
|
constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
1420
|
+
var _a, _b, _c;
|
|
1077
1421
|
const [requestHeaders, params] = await Promise.all([
|
|
1078
1422
|
resolveHeaders(this.options.headers),
|
|
1079
1423
|
this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
|
|
@@ -1082,7 +1426,13 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
|
1082
1426
|
const fetchUrl = new URL(url);
|
|
1083
1427
|
if (params) {
|
|
1084
1428
|
if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table);
|
|
1085
|
-
if (params.where
|
|
1429
|
+
if (params.where && typeof params.where === `string`) {
|
|
1430
|
+
const encodedWhere = encodeWhereClause(
|
|
1431
|
+
params.where,
|
|
1432
|
+
(_a = this.options.columnMapper) == null ? void 0 : _a.encode
|
|
1433
|
+
);
|
|
1434
|
+
setQueryParam(fetchUrl, WHERE_QUERY_PARAM, encodedWhere);
|
|
1435
|
+
}
|
|
1086
1436
|
if (params.columns)
|
|
1087
1437
|
setQueryParam(fetchUrl, COLUMNS_QUERY_PARAM, params.columns);
|
|
1088
1438
|
if (params.replica) setQueryParam(fetchUrl, REPLICA_PARAM, params.replica);
|
|
@@ -1099,20 +1449,34 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
|
1099
1449
|
}
|
|
1100
1450
|
}
|
|
1101
1451
|
if (subsetParams) {
|
|
1102
|
-
if (subsetParams.where)
|
|
1103
|
-
|
|
1452
|
+
if (subsetParams.where && typeof subsetParams.where === `string`) {
|
|
1453
|
+
const encodedWhere = encodeWhereClause(
|
|
1454
|
+
subsetParams.where,
|
|
1455
|
+
(_b = this.options.columnMapper) == null ? void 0 : _b.encode
|
|
1456
|
+
);
|
|
1457
|
+
setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, encodedWhere);
|
|
1458
|
+
}
|
|
1104
1459
|
if (subsetParams.params)
|
|
1105
|
-
|
|
1460
|
+
fetchUrl.searchParams.set(
|
|
1461
|
+
SUBSET_PARAM_WHERE_PARAMS,
|
|
1462
|
+
JSON.stringify(subsetParams.params)
|
|
1463
|
+
);
|
|
1106
1464
|
if (subsetParams.limit)
|
|
1107
1465
|
setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit);
|
|
1108
1466
|
if (subsetParams.offset)
|
|
1109
1467
|
setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset);
|
|
1110
|
-
if (subsetParams.orderBy)
|
|
1111
|
-
|
|
1468
|
+
if (subsetParams.orderBy && typeof subsetParams.orderBy === `string`) {
|
|
1469
|
+
const encodedOrderBy = encodeWhereClause(
|
|
1470
|
+
subsetParams.orderBy,
|
|
1471
|
+
(_c = this.options.columnMapper) == null ? void 0 : _c.encode
|
|
1472
|
+
);
|
|
1473
|
+
setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, encodedOrderBy);
|
|
1474
|
+
}
|
|
1112
1475
|
}
|
|
1113
1476
|
fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
|
|
1114
1477
|
fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
|
|
1115
|
-
|
|
1478
|
+
const isSnapshotRequest = subsetParams !== void 0;
|
|
1479
|
+
if (__privateGet(this, _isUpToDate) && !isSnapshotRequest) {
|
|
1116
1480
|
if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
|
|
1117
1481
|
fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
|
|
1118
1482
|
}
|
|
@@ -1165,11 +1529,7 @@ onInitialResponse_fn = async function(response) {
|
|
|
1165
1529
|
if (liveCacheBuster) {
|
|
1166
1530
|
__privateSet(this, _liveCacheBuster, liveCacheBuster);
|
|
1167
1531
|
}
|
|
1168
|
-
|
|
1169
|
-
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
|
|
1170
|
-
return schemaHeader ? JSON.parse(schemaHeader) : {};
|
|
1171
|
-
};
|
|
1172
|
-
__privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchema());
|
|
1532
|
+
__privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchemaFromHeaders(headers));
|
|
1173
1533
|
if (status === 204) {
|
|
1174
1534
|
__privateSet(this, _lastSyncedAt, Date.now());
|
|
1175
1535
|
}
|
|
@@ -1190,6 +1550,17 @@ onMessages_fn = async function(batch, isSseMessage = false) {
|
|
|
1190
1550
|
__privateSet(this, _isUpToDate, true);
|
|
1191
1551
|
__privateSet(this, _isMidStream, false);
|
|
1192
1552
|
(_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
|
|
1553
|
+
if (__privateGet(this, _ShapeStream_instances, replayMode_get) && !isSseMessage) {
|
|
1554
|
+
const currentCursor = __privateGet(this, _liveCacheBuster);
|
|
1555
|
+
if (currentCursor === __privateGet(this, _lastSeenCursor)) {
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
__privateSet(this, _lastSeenCursor, void 0);
|
|
1560
|
+
if (__privateGet(this, _currentFetchUrl)) {
|
|
1561
|
+
const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
|
|
1562
|
+
upToDateTracker.recordUpToDate(shapeKey, __privateGet(this, _liveCacheBuster));
|
|
1563
|
+
}
|
|
1193
1564
|
}
|
|
1194
1565
|
const messagesToProcess = batch.filter((message) => {
|
|
1195
1566
|
if (isChangeMessage(message)) {
|
|
@@ -1202,6 +1573,14 @@ onMessages_fn = async function(batch, isSseMessage = false) {
|
|
|
1202
1573
|
};
|
|
1203
1574
|
fetchShape_fn = async function(opts) {
|
|
1204
1575
|
var _a;
|
|
1576
|
+
__privateSet(this, _currentFetchUrl, opts.fetchUrl);
|
|
1577
|
+
if (!__privateGet(this, _isUpToDate) && !__privateGet(this, _ShapeStream_instances, replayMode_get)) {
|
|
1578
|
+
const shapeKey = canonicalShapeKey(opts.fetchUrl);
|
|
1579
|
+
const lastSeenCursor = upToDateTracker.shouldEnterReplayMode(shapeKey);
|
|
1580
|
+
if (lastSeenCursor) {
|
|
1581
|
+
__privateSet(this, _lastSeenCursor, lastSeenCursor);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1205
1584
|
const useSse = (_a = this.options.liveSse) != null ? _a : this.options.experimentalLiveSse;
|
|
1206
1585
|
if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling)) {
|
|
1207
1586
|
opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
|
|
@@ -1382,31 +1761,20 @@ reset_fn = function(handle) {
|
|
|
1382
1761
|
__privateSet(this, _consecutiveShortSseConnections, 0);
|
|
1383
1762
|
__privateSet(this, _sseFallbackToLongPolling, false);
|
|
1384
1763
|
};
|
|
1385
|
-
fetchSnapshot_fn = async function(url, headers) {
|
|
1386
|
-
const response = await __privateGet(this, _fetchClient2).call(this, url.toString(), { headers });
|
|
1387
|
-
if (!response.ok) {
|
|
1388
|
-
throw new FetchError(
|
|
1389
|
-
response.status,
|
|
1390
|
-
void 0,
|
|
1391
|
-
void 0,
|
|
1392
|
-
Object.fromEntries([...response.headers.entries()]),
|
|
1393
|
-
url.toString()
|
|
1394
|
-
);
|
|
1395
|
-
}
|
|
1396
|
-
const { metadata, data } = await response.json();
|
|
1397
|
-
const batch = __privateGet(this, _messageParser).parse(
|
|
1398
|
-
JSON.stringify(data),
|
|
1399
|
-
__privateGet(this, _schema)
|
|
1400
|
-
);
|
|
1401
|
-
return {
|
|
1402
|
-
metadata,
|
|
1403
|
-
data: batch
|
|
1404
|
-
};
|
|
1405
|
-
};
|
|
1406
1764
|
ShapeStream.Replica = {
|
|
1407
1765
|
FULL: `full`,
|
|
1408
1766
|
DEFAULT: `default`
|
|
1409
1767
|
};
|
|
1768
|
+
function getSchemaFromHeaders(headers, options) {
|
|
1769
|
+
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
|
|
1770
|
+
if (!schemaHeader) {
|
|
1771
|
+
if ((options == null ? void 0 : options.required) && (options == null ? void 0 : options.url)) {
|
|
1772
|
+
throw new MissingHeadersError(options.url, [SHAPE_SCHEMA_HEADER]);
|
|
1773
|
+
}
|
|
1774
|
+
return {};
|
|
1775
|
+
}
|
|
1776
|
+
return JSON.parse(schemaHeader);
|
|
1777
|
+
}
|
|
1410
1778
|
function validateParams(params) {
|
|
1411
1779
|
if (!params) return;
|
|
1412
1780
|
const reservedParams = Object.keys(params).filter(
|
|
@@ -1665,9 +2033,13 @@ export {
|
|
|
1665
2033
|
FetchError,
|
|
1666
2034
|
Shape,
|
|
1667
2035
|
ShapeStream,
|
|
2036
|
+
camelToSnake,
|
|
2037
|
+
createColumnMapper,
|
|
1668
2038
|
isChangeMessage,
|
|
1669
2039
|
isControlMessage,
|
|
1670
2040
|
isVisibleInSnapshot,
|
|
1671
|
-
resolveValue
|
|
2041
|
+
resolveValue,
|
|
2042
|
+
snakeCamelMapper,
|
|
2043
|
+
snakeToCamel
|
|
1672
2044
|
};
|
|
1673
2045
|
//# sourceMappingURL=index.legacy-esm.js.map
|