@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.mjs
CHANGED
|
@@ -225,15 +225,37 @@ var MessageParser = class {
|
|
|
225
225
|
parse(messages, schema) {
|
|
226
226
|
return JSON.parse(messages, (key, value) => {
|
|
227
227
|
if ((key === `value` || key === `old_value`) && typeof value === `object` && value !== null) {
|
|
228
|
-
|
|
229
|
-
Object.keys(row).forEach((key2) => {
|
|
230
|
-
row[key2] = this.parseRow(key2, row[key2], schema);
|
|
231
|
-
});
|
|
232
|
-
if (this.transformer) value = this.transformer(value);
|
|
228
|
+
return this.transformMessageValue(value, schema);
|
|
233
229
|
}
|
|
234
230
|
return value;
|
|
235
231
|
});
|
|
236
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Parse an array of ChangeMessages from a snapshot response.
|
|
235
|
+
* Applies type parsing and transformations to the value and old_value properties.
|
|
236
|
+
*/
|
|
237
|
+
parseSnapshotData(messages, schema) {
|
|
238
|
+
return messages.map((message) => {
|
|
239
|
+
const msg = message;
|
|
240
|
+
if (msg.value && typeof msg.value === `object` && msg.value !== null) {
|
|
241
|
+
msg.value = this.transformMessageValue(msg.value, schema);
|
|
242
|
+
}
|
|
243
|
+
if (msg.old_value && typeof msg.old_value === `object` && msg.old_value !== null) {
|
|
244
|
+
msg.old_value = this.transformMessageValue(msg.old_value, schema);
|
|
245
|
+
}
|
|
246
|
+
return msg;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Transform a message value or old_value object by parsing its columns.
|
|
251
|
+
*/
|
|
252
|
+
transformMessageValue(value, schema) {
|
|
253
|
+
const row = value;
|
|
254
|
+
Object.keys(row).forEach((key) => {
|
|
255
|
+
row[key] = this.parseRow(key, row[key], schema);
|
|
256
|
+
});
|
|
257
|
+
return this.transformer ? this.transformer(row) : row;
|
|
258
|
+
}
|
|
237
259
|
// Parses the message values using the provided parser based on the schema information
|
|
238
260
|
parseRow(key, value, schema) {
|
|
239
261
|
var _b;
|
|
@@ -269,6 +291,151 @@ function makeNullableParser(parser, columnInfo, columnName) {
|
|
|
269
291
|
};
|
|
270
292
|
}
|
|
271
293
|
|
|
294
|
+
// src/column-mapper.ts
|
|
295
|
+
function snakeToCamel(str) {
|
|
296
|
+
var _a, _b, _c, _d;
|
|
297
|
+
const leadingUnderscores = (_b = (_a = str.match(/^_+/)) == null ? void 0 : _a[0]) != null ? _b : ``;
|
|
298
|
+
const withoutLeading = str.slice(leadingUnderscores.length);
|
|
299
|
+
const trailingUnderscores = (_d = (_c = withoutLeading.match(/_+$/)) == null ? void 0 : _c[0]) != null ? _d : ``;
|
|
300
|
+
const core = trailingUnderscores ? withoutLeading.slice(
|
|
301
|
+
0,
|
|
302
|
+
withoutLeading.length - trailingUnderscores.length
|
|
303
|
+
) : withoutLeading;
|
|
304
|
+
const normalized = core.toLowerCase();
|
|
305
|
+
const camelCased = normalized.replace(
|
|
306
|
+
/_+([a-z])/g,
|
|
307
|
+
(_, letter) => letter.toUpperCase()
|
|
308
|
+
);
|
|
309
|
+
return leadingUnderscores + camelCased + trailingUnderscores;
|
|
310
|
+
}
|
|
311
|
+
function camelToSnake(str) {
|
|
312
|
+
return str.replace(/([a-z])([A-Z])/g, `$1_$2`).replace(/([A-Z]+)([A-Z][a-z])/g, `$1_$2`).toLowerCase();
|
|
313
|
+
}
|
|
314
|
+
function createColumnMapper(mapping) {
|
|
315
|
+
const reverseMapping = {};
|
|
316
|
+
for (const [dbName, appName] of Object.entries(mapping)) {
|
|
317
|
+
reverseMapping[appName] = dbName;
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
decode: (dbColumnName) => {
|
|
321
|
+
var _a;
|
|
322
|
+
return (_a = mapping[dbColumnName]) != null ? _a : dbColumnName;
|
|
323
|
+
},
|
|
324
|
+
encode: (appColumnName) => {
|
|
325
|
+
var _a;
|
|
326
|
+
return (_a = reverseMapping[appColumnName]) != null ? _a : appColumnName;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function encodeWhereClause(whereClause, encode) {
|
|
331
|
+
if (!whereClause || !encode) return whereClause != null ? whereClause : ``;
|
|
332
|
+
const sqlKeywords = /* @__PURE__ */ new Set([
|
|
333
|
+
`SELECT`,
|
|
334
|
+
`FROM`,
|
|
335
|
+
`WHERE`,
|
|
336
|
+
`AND`,
|
|
337
|
+
`OR`,
|
|
338
|
+
`NOT`,
|
|
339
|
+
`IN`,
|
|
340
|
+
`IS`,
|
|
341
|
+
`NULL`,
|
|
342
|
+
`NULLS`,
|
|
343
|
+
`FIRST`,
|
|
344
|
+
`LAST`,
|
|
345
|
+
`TRUE`,
|
|
346
|
+
`FALSE`,
|
|
347
|
+
`LIKE`,
|
|
348
|
+
`ILIKE`,
|
|
349
|
+
`BETWEEN`,
|
|
350
|
+
`ASC`,
|
|
351
|
+
`DESC`,
|
|
352
|
+
`LIMIT`,
|
|
353
|
+
`OFFSET`,
|
|
354
|
+
`ORDER`,
|
|
355
|
+
`BY`,
|
|
356
|
+
`GROUP`,
|
|
357
|
+
`HAVING`,
|
|
358
|
+
`DISTINCT`,
|
|
359
|
+
`AS`,
|
|
360
|
+
`ON`,
|
|
361
|
+
`JOIN`,
|
|
362
|
+
`LEFT`,
|
|
363
|
+
`RIGHT`,
|
|
364
|
+
`INNER`,
|
|
365
|
+
`OUTER`,
|
|
366
|
+
`CROSS`,
|
|
367
|
+
`CASE`,
|
|
368
|
+
`WHEN`,
|
|
369
|
+
`THEN`,
|
|
370
|
+
`ELSE`,
|
|
371
|
+
`END`,
|
|
372
|
+
`CAST`,
|
|
373
|
+
`LOWER`,
|
|
374
|
+
`UPPER`,
|
|
375
|
+
`COALESCE`,
|
|
376
|
+
`NULLIF`
|
|
377
|
+
]);
|
|
378
|
+
const quotedRanges = [];
|
|
379
|
+
let pos = 0;
|
|
380
|
+
while (pos < whereClause.length) {
|
|
381
|
+
const ch = whereClause[pos];
|
|
382
|
+
if (ch === `'` || ch === `"`) {
|
|
383
|
+
const start = pos;
|
|
384
|
+
const quoteChar = ch;
|
|
385
|
+
pos++;
|
|
386
|
+
while (pos < whereClause.length) {
|
|
387
|
+
if (whereClause[pos] === quoteChar) {
|
|
388
|
+
if (whereClause[pos + 1] === quoteChar) {
|
|
389
|
+
pos += 2;
|
|
390
|
+
} else {
|
|
391
|
+
pos++;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
pos++;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
quotedRanges.push({ start, end: pos });
|
|
399
|
+
} else {
|
|
400
|
+
pos++;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const isInQuotedString = (pos2) => {
|
|
404
|
+
return quotedRanges.some((range) => pos2 >= range.start && pos2 < range.end);
|
|
405
|
+
};
|
|
406
|
+
const identifierPattern = new RegExp("(?<![a-zA-Z0-9_])([a-zA-Z_][a-zA-Z0-9_]*)(?![a-zA-Z0-9_])", "g");
|
|
407
|
+
return whereClause.replace(identifierPattern, (match, _p1, offset) => {
|
|
408
|
+
if (isInQuotedString(offset)) {
|
|
409
|
+
return match;
|
|
410
|
+
}
|
|
411
|
+
if (sqlKeywords.has(match.toUpperCase())) {
|
|
412
|
+
return match;
|
|
413
|
+
}
|
|
414
|
+
if (match.startsWith(`$`)) {
|
|
415
|
+
return match;
|
|
416
|
+
}
|
|
417
|
+
const encoded = encode(match);
|
|
418
|
+
return encoded;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
function snakeCamelMapper(schema) {
|
|
422
|
+
if (schema) {
|
|
423
|
+
const mapping = {};
|
|
424
|
+
for (const dbColumn of Object.keys(schema)) {
|
|
425
|
+
mapping[dbColumn] = snakeToCamel(dbColumn);
|
|
426
|
+
}
|
|
427
|
+
return createColumnMapper(mapping);
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
decode: (dbColumnName) => {
|
|
431
|
+
return snakeToCamel(dbColumnName);
|
|
432
|
+
},
|
|
433
|
+
encode: (appColumnName) => {
|
|
434
|
+
return camelToSnake(appColumnName);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
272
439
|
// src/helpers.ts
|
|
273
440
|
function isChangeMessage(message) {
|
|
274
441
|
return `key` in message;
|
|
@@ -664,6 +831,127 @@ var ExpiredShapesCache = class {
|
|
|
664
831
|
};
|
|
665
832
|
var expiredShapesCache = new ExpiredShapesCache();
|
|
666
833
|
|
|
834
|
+
// src/up-to-date-tracker.ts
|
|
835
|
+
var UpToDateTracker = class {
|
|
836
|
+
constructor() {
|
|
837
|
+
this.data = {};
|
|
838
|
+
this.storageKey = `electric_up_to_date_tracker`;
|
|
839
|
+
this.cacheTTL = 6e4;
|
|
840
|
+
// 60s to match typical CDN s-maxage cache duration
|
|
841
|
+
this.maxEntries = 250;
|
|
842
|
+
this.writeThrottleMs = 6e4;
|
|
843
|
+
// Throttle localStorage writes to once per 60s
|
|
844
|
+
this.lastWriteTime = 0;
|
|
845
|
+
this.load();
|
|
846
|
+
this.cleanup();
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Records that a shape received an up-to-date message with a specific cursor.
|
|
850
|
+
* This timestamp and cursor are used to detect cache replay scenarios.
|
|
851
|
+
* Updates in-memory immediately, but throttles localStorage writes.
|
|
852
|
+
*/
|
|
853
|
+
recordUpToDate(shapeKey, cursor) {
|
|
854
|
+
this.data[shapeKey] = {
|
|
855
|
+
timestamp: Date.now(),
|
|
856
|
+
cursor
|
|
857
|
+
};
|
|
858
|
+
const keys = Object.keys(this.data);
|
|
859
|
+
if (keys.length > this.maxEntries) {
|
|
860
|
+
const oldest = keys.reduce(
|
|
861
|
+
(min, k) => this.data[k].timestamp < this.data[min].timestamp ? k : min
|
|
862
|
+
);
|
|
863
|
+
delete this.data[oldest];
|
|
864
|
+
}
|
|
865
|
+
this.scheduleSave();
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Schedules a throttled save to localStorage.
|
|
869
|
+
* Writes immediately if enough time has passed, otherwise schedules for later.
|
|
870
|
+
*/
|
|
871
|
+
scheduleSave() {
|
|
872
|
+
const now = Date.now();
|
|
873
|
+
const timeSinceLastWrite = now - this.lastWriteTime;
|
|
874
|
+
if (timeSinceLastWrite >= this.writeThrottleMs) {
|
|
875
|
+
this.lastWriteTime = now;
|
|
876
|
+
this.save();
|
|
877
|
+
} else if (!this.pendingSaveTimer) {
|
|
878
|
+
const delay = this.writeThrottleMs - timeSinceLastWrite;
|
|
879
|
+
this.pendingSaveTimer = setTimeout(() => {
|
|
880
|
+
this.lastWriteTime = Date.now();
|
|
881
|
+
this.pendingSaveTimer = void 0;
|
|
882
|
+
this.save();
|
|
883
|
+
}, delay);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Checks if we should enter replay mode for this shape.
|
|
888
|
+
* Returns the last seen cursor if there's a recent up-to-date (< 60s),
|
|
889
|
+
* which means we'll likely be replaying cached responses.
|
|
890
|
+
* Returns null if no recent up-to-date exists.
|
|
891
|
+
*/
|
|
892
|
+
shouldEnterReplayMode(shapeKey) {
|
|
893
|
+
const entry = this.data[shapeKey];
|
|
894
|
+
if (!entry) {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
const age = Date.now() - entry.timestamp;
|
|
898
|
+
if (age >= this.cacheTTL) {
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
return entry.cursor;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Cleans up expired entries from the cache.
|
|
905
|
+
* Called on initialization and can be called periodically.
|
|
906
|
+
*/
|
|
907
|
+
cleanup() {
|
|
908
|
+
const now = Date.now();
|
|
909
|
+
const keys = Object.keys(this.data);
|
|
910
|
+
let modified = false;
|
|
911
|
+
for (const key of keys) {
|
|
912
|
+
const age = now - this.data[key].timestamp;
|
|
913
|
+
if (age > this.cacheTTL) {
|
|
914
|
+
delete this.data[key];
|
|
915
|
+
modified = true;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (modified) {
|
|
919
|
+
this.save();
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
save() {
|
|
923
|
+
if (typeof localStorage === `undefined`) return;
|
|
924
|
+
try {
|
|
925
|
+
localStorage.setItem(this.storageKey, JSON.stringify(this.data));
|
|
926
|
+
} catch (e) {
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
load() {
|
|
930
|
+
if (typeof localStorage === `undefined`) return;
|
|
931
|
+
try {
|
|
932
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
933
|
+
if (stored) {
|
|
934
|
+
this.data = JSON.parse(stored);
|
|
935
|
+
}
|
|
936
|
+
} catch (e) {
|
|
937
|
+
this.data = {};
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Clears all tracked up-to-date timestamps.
|
|
942
|
+
* Useful for testing or manual cache invalidation.
|
|
943
|
+
*/
|
|
944
|
+
clear() {
|
|
945
|
+
this.data = {};
|
|
946
|
+
if (this.pendingSaveTimer) {
|
|
947
|
+
clearTimeout(this.pendingSaveTimer);
|
|
948
|
+
this.pendingSaveTimer = void 0;
|
|
949
|
+
}
|
|
950
|
+
this.save();
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
var upToDateTracker = new UpToDateTracker();
|
|
954
|
+
|
|
667
955
|
// src/snapshot-tracker.ts
|
|
668
956
|
var SnapshotTracker = class {
|
|
669
957
|
constructor() {
|
|
@@ -781,7 +1069,7 @@ function canonicalShapeKey(url) {
|
|
|
781
1069
|
cleanUrl.searchParams.sort();
|
|
782
1070
|
return cleanUrl.toString();
|
|
783
1071
|
}
|
|
784
|
-
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
|
|
1072
|
+
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;
|
|
785
1073
|
var ShapeStream = class {
|
|
786
1074
|
constructor(options) {
|
|
787
1075
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -816,6 +1104,10 @@ var ShapeStream = class {
|
|
|
816
1104
|
// counter for concurrent snapshot requests
|
|
817
1105
|
__privateAdd(this, _midStreamPromise);
|
|
818
1106
|
__privateAdd(this, _midStreamPromiseResolver);
|
|
1107
|
+
__privateAdd(this, _lastSeenCursor);
|
|
1108
|
+
// Last seen cursor from previous session (used to detect cached responses)
|
|
1109
|
+
__privateAdd(this, _currentFetchUrl);
|
|
1110
|
+
// Current fetch URL for computing shape key
|
|
819
1111
|
__privateAdd(this, _lastSseConnectionStartTime);
|
|
820
1112
|
__privateAdd(this, _minSseConnectionDuration, 1e3);
|
|
821
1113
|
// Minimum expected SSE connection duration (1 second)
|
|
@@ -834,10 +1126,21 @@ var ShapeStream = class {
|
|
|
834
1126
|
__privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
|
|
835
1127
|
__privateSet(this, _liveCacheBuster, ``);
|
|
836
1128
|
__privateSet(this, _shapeHandle, this.options.handle);
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1129
|
+
let transformer;
|
|
1130
|
+
if (options.columnMapper) {
|
|
1131
|
+
const applyColumnMapper = (row) => {
|
|
1132
|
+
const result = {};
|
|
1133
|
+
for (const [dbKey, value] of Object.entries(row)) {
|
|
1134
|
+
const appKey = options.columnMapper.decode(dbKey);
|
|
1135
|
+
result[appKey] = value;
|
|
1136
|
+
}
|
|
1137
|
+
return result;
|
|
1138
|
+
};
|
|
1139
|
+
transformer = options.transformer ? (row) => options.transformer(applyColumnMapper(row)) : applyColumnMapper;
|
|
1140
|
+
} else {
|
|
1141
|
+
transformer = options.transformer;
|
|
1142
|
+
}
|
|
1143
|
+
__privateSet(this, _messageParser, new MessageParser(options.parser, transformer));
|
|
841
1144
|
__privateSet(this, _onError, this.options.onError);
|
|
842
1145
|
__privateSet(this, _mode, (_b = this.options.log) != null ? _b : `full`);
|
|
843
1146
|
const baseFetchClient = (_c = options.fetchClient) != null ? _c : (...args) => fetch(...args);
|
|
@@ -928,7 +1231,7 @@ var ShapeStream = class {
|
|
|
928
1231
|
});
|
|
929
1232
|
}
|
|
930
1233
|
/**
|
|
931
|
-
* Request a snapshot for subset of data.
|
|
1234
|
+
* Request a snapshot for subset of data and inject it into the subscribed data stream.
|
|
932
1235
|
*
|
|
933
1236
|
* Only available when mode is `changes_only`.
|
|
934
1237
|
* Returns the insertion point & the data, but more importantly injects the data
|
|
@@ -955,8 +1258,7 @@ var ShapeStream = class {
|
|
|
955
1258
|
if (__privateGet(this, _activeSnapshotRequests) === 1) {
|
|
956
1259
|
__privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
|
|
957
1260
|
}
|
|
958
|
-
const {
|
|
959
|
-
const { metadata, data } = yield __privateMethod(this, _ShapeStream_instances, fetchSnapshot_fn).call(this, fetchUrl, requestHeaders);
|
|
1261
|
+
const { metadata, data } = yield this.fetchSnapshot(opts);
|
|
960
1262
|
const dataWithEndBoundary = data.concat([
|
|
961
1263
|
{ headers: __spreadValues({ control: `snapshot-end` }, metadata) }
|
|
962
1264
|
]);
|
|
@@ -977,6 +1279,44 @@ var ShapeStream = class {
|
|
|
977
1279
|
}
|
|
978
1280
|
});
|
|
979
1281
|
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Fetch a snapshot for subset of data.
|
|
1284
|
+
* Returns the metadata and the data, but does not inject it into the subscribed data stream.
|
|
1285
|
+
*
|
|
1286
|
+
* @param opts - The options for the snapshot request.
|
|
1287
|
+
* @returns The metadata and the data for the snapshot.
|
|
1288
|
+
*/
|
|
1289
|
+
fetchSnapshot(opts) {
|
|
1290
|
+
return __async(this, null, function* () {
|
|
1291
|
+
var _a;
|
|
1292
|
+
const { fetchUrl, requestHeaders } = yield __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
|
|
1293
|
+
const response = yield __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), {
|
|
1294
|
+
headers: requestHeaders
|
|
1295
|
+
});
|
|
1296
|
+
if (!response.ok) {
|
|
1297
|
+
throw new FetchError(
|
|
1298
|
+
response.status,
|
|
1299
|
+
void 0,
|
|
1300
|
+
void 0,
|
|
1301
|
+
Object.fromEntries([...response.headers.entries()]),
|
|
1302
|
+
fetchUrl.toString()
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
const schema = (_a = __privateGet(this, _schema)) != null ? _a : getSchemaFromHeaders(response.headers, {
|
|
1306
|
+
required: true,
|
|
1307
|
+
url: fetchUrl.toString()
|
|
1308
|
+
});
|
|
1309
|
+
const { metadata, data: rawData } = yield response.json();
|
|
1310
|
+
const data = __privateGet(this, _messageParser).parseSnapshotData(
|
|
1311
|
+
rawData,
|
|
1312
|
+
schema
|
|
1313
|
+
);
|
|
1314
|
+
return {
|
|
1315
|
+
metadata,
|
|
1316
|
+
data
|
|
1317
|
+
};
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
980
1320
|
};
|
|
981
1321
|
_error = new WeakMap();
|
|
982
1322
|
_fetchClient2 = new WeakMap();
|
|
@@ -1005,6 +1345,8 @@ _snapshotTracker = new WeakMap();
|
|
|
1005
1345
|
_activeSnapshotRequests = new WeakMap();
|
|
1006
1346
|
_midStreamPromise = new WeakMap();
|
|
1007
1347
|
_midStreamPromiseResolver = new WeakMap();
|
|
1348
|
+
_lastSeenCursor = new WeakMap();
|
|
1349
|
+
_currentFetchUrl = new WeakMap();
|
|
1008
1350
|
_lastSseConnectionStartTime = new WeakMap();
|
|
1009
1351
|
_minSseConnectionDuration = new WeakMap();
|
|
1010
1352
|
_consecutiveShortSseConnections = new WeakMap();
|
|
@@ -1014,6 +1356,9 @@ _sseBackoffBaseDelay = new WeakMap();
|
|
|
1014
1356
|
_sseBackoffMaxDelay = new WeakMap();
|
|
1015
1357
|
_unsubscribeFromVisibilityChanges = new WeakMap();
|
|
1016
1358
|
_ShapeStream_instances = new WeakSet();
|
|
1359
|
+
replayMode_get = function() {
|
|
1360
|
+
return __privateGet(this, _lastSeenCursor) !== void 0;
|
|
1361
|
+
};
|
|
1017
1362
|
start_fn = function() {
|
|
1018
1363
|
return __async(this, null, function* () {
|
|
1019
1364
|
var _a, _b, _c, _d, _e;
|
|
@@ -1113,6 +1458,7 @@ requestShape_fn = function() {
|
|
|
1113
1458
|
};
|
|
1114
1459
|
constructUrl_fn = function(url, resumingFromPause, subsetParams) {
|
|
1115
1460
|
return __async(this, null, function* () {
|
|
1461
|
+
var _a, _b, _c;
|
|
1116
1462
|
const [requestHeaders, params] = yield Promise.all([
|
|
1117
1463
|
resolveHeaders(this.options.headers),
|
|
1118
1464
|
this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
|
|
@@ -1121,7 +1467,13 @@ constructUrl_fn = function(url, resumingFromPause, subsetParams) {
|
|
|
1121
1467
|
const fetchUrl = new URL(url);
|
|
1122
1468
|
if (params) {
|
|
1123
1469
|
if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table);
|
|
1124
|
-
if (params.where
|
|
1470
|
+
if (params.where && typeof params.where === `string`) {
|
|
1471
|
+
const encodedWhere = encodeWhereClause(
|
|
1472
|
+
params.where,
|
|
1473
|
+
(_a = this.options.columnMapper) == null ? void 0 : _a.encode
|
|
1474
|
+
);
|
|
1475
|
+
setQueryParam(fetchUrl, WHERE_QUERY_PARAM, encodedWhere);
|
|
1476
|
+
}
|
|
1125
1477
|
if (params.columns)
|
|
1126
1478
|
setQueryParam(fetchUrl, COLUMNS_QUERY_PARAM, params.columns);
|
|
1127
1479
|
if (params.replica) setQueryParam(fetchUrl, REPLICA_PARAM, params.replica);
|
|
@@ -1138,20 +1490,34 @@ constructUrl_fn = function(url, resumingFromPause, subsetParams) {
|
|
|
1138
1490
|
}
|
|
1139
1491
|
}
|
|
1140
1492
|
if (subsetParams) {
|
|
1141
|
-
if (subsetParams.where)
|
|
1142
|
-
|
|
1493
|
+
if (subsetParams.where && typeof subsetParams.where === `string`) {
|
|
1494
|
+
const encodedWhere = encodeWhereClause(
|
|
1495
|
+
subsetParams.where,
|
|
1496
|
+
(_b = this.options.columnMapper) == null ? void 0 : _b.encode
|
|
1497
|
+
);
|
|
1498
|
+
setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, encodedWhere);
|
|
1499
|
+
}
|
|
1143
1500
|
if (subsetParams.params)
|
|
1144
|
-
|
|
1501
|
+
fetchUrl.searchParams.set(
|
|
1502
|
+
SUBSET_PARAM_WHERE_PARAMS,
|
|
1503
|
+
JSON.stringify(subsetParams.params)
|
|
1504
|
+
);
|
|
1145
1505
|
if (subsetParams.limit)
|
|
1146
1506
|
setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit);
|
|
1147
1507
|
if (subsetParams.offset)
|
|
1148
1508
|
setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset);
|
|
1149
|
-
if (subsetParams.orderBy)
|
|
1150
|
-
|
|
1509
|
+
if (subsetParams.orderBy && typeof subsetParams.orderBy === `string`) {
|
|
1510
|
+
const encodedOrderBy = encodeWhereClause(
|
|
1511
|
+
subsetParams.orderBy,
|
|
1512
|
+
(_c = this.options.columnMapper) == null ? void 0 : _c.encode
|
|
1513
|
+
);
|
|
1514
|
+
setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, encodedOrderBy);
|
|
1515
|
+
}
|
|
1151
1516
|
}
|
|
1152
1517
|
fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
|
|
1153
1518
|
fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
|
|
1154
|
-
|
|
1519
|
+
const isSnapshotRequest = subsetParams !== void 0;
|
|
1520
|
+
if (__privateGet(this, _isUpToDate) && !isSnapshotRequest) {
|
|
1155
1521
|
if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
|
|
1156
1522
|
fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
|
|
1157
1523
|
}
|
|
@@ -1208,11 +1574,7 @@ onInitialResponse_fn = function(response) {
|
|
|
1208
1574
|
if (liveCacheBuster) {
|
|
1209
1575
|
__privateSet(this, _liveCacheBuster, liveCacheBuster);
|
|
1210
1576
|
}
|
|
1211
|
-
|
|
1212
|
-
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
|
|
1213
|
-
return schemaHeader ? JSON.parse(schemaHeader) : {};
|
|
1214
|
-
};
|
|
1215
|
-
__privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchema());
|
|
1577
|
+
__privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchemaFromHeaders(headers));
|
|
1216
1578
|
if (status === 204) {
|
|
1217
1579
|
__privateSet(this, _lastSyncedAt, Date.now());
|
|
1218
1580
|
}
|
|
@@ -1235,6 +1597,17 @@ onMessages_fn = function(batch, isSseMessage = false) {
|
|
|
1235
1597
|
__privateSet(this, _isUpToDate, true);
|
|
1236
1598
|
__privateSet(this, _isMidStream, false);
|
|
1237
1599
|
(_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
|
|
1600
|
+
if (__privateGet(this, _ShapeStream_instances, replayMode_get) && !isSseMessage) {
|
|
1601
|
+
const currentCursor = __privateGet(this, _liveCacheBuster);
|
|
1602
|
+
if (currentCursor === __privateGet(this, _lastSeenCursor)) {
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
__privateSet(this, _lastSeenCursor, void 0);
|
|
1607
|
+
if (__privateGet(this, _currentFetchUrl)) {
|
|
1608
|
+
const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
|
|
1609
|
+
upToDateTracker.recordUpToDate(shapeKey, __privateGet(this, _liveCacheBuster));
|
|
1610
|
+
}
|
|
1238
1611
|
}
|
|
1239
1612
|
const messagesToProcess = batch.filter((message) => {
|
|
1240
1613
|
if (isChangeMessage(message)) {
|
|
@@ -1249,6 +1622,14 @@ onMessages_fn = function(batch, isSseMessage = false) {
|
|
|
1249
1622
|
fetchShape_fn = function(opts) {
|
|
1250
1623
|
return __async(this, null, function* () {
|
|
1251
1624
|
var _a;
|
|
1625
|
+
__privateSet(this, _currentFetchUrl, opts.fetchUrl);
|
|
1626
|
+
if (!__privateGet(this, _isUpToDate) && !__privateGet(this, _ShapeStream_instances, replayMode_get)) {
|
|
1627
|
+
const shapeKey = canonicalShapeKey(opts.fetchUrl);
|
|
1628
|
+
const lastSeenCursor = upToDateTracker.shouldEnterReplayMode(shapeKey);
|
|
1629
|
+
if (lastSeenCursor) {
|
|
1630
|
+
__privateSet(this, _lastSeenCursor, lastSeenCursor);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1252
1633
|
const useSse = (_a = this.options.liveSse) != null ? _a : this.options.experimentalLiveSse;
|
|
1253
1634
|
if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling)) {
|
|
1254
1635
|
opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
|
|
@@ -1440,33 +1821,20 @@ reset_fn = function(handle) {
|
|
|
1440
1821
|
__privateSet(this, _consecutiveShortSseConnections, 0);
|
|
1441
1822
|
__privateSet(this, _sseFallbackToLongPolling, false);
|
|
1442
1823
|
};
|
|
1443
|
-
fetchSnapshot_fn = function(url, headers) {
|
|
1444
|
-
return __async(this, null, function* () {
|
|
1445
|
-
const response = yield __privateGet(this, _fetchClient2).call(this, url.toString(), { headers });
|
|
1446
|
-
if (!response.ok) {
|
|
1447
|
-
throw new FetchError(
|
|
1448
|
-
response.status,
|
|
1449
|
-
void 0,
|
|
1450
|
-
void 0,
|
|
1451
|
-
Object.fromEntries([...response.headers.entries()]),
|
|
1452
|
-
url.toString()
|
|
1453
|
-
);
|
|
1454
|
-
}
|
|
1455
|
-
const { metadata, data } = yield response.json();
|
|
1456
|
-
const batch = __privateGet(this, _messageParser).parse(
|
|
1457
|
-
JSON.stringify(data),
|
|
1458
|
-
__privateGet(this, _schema)
|
|
1459
|
-
);
|
|
1460
|
-
return {
|
|
1461
|
-
metadata,
|
|
1462
|
-
data: batch
|
|
1463
|
-
};
|
|
1464
|
-
});
|
|
1465
|
-
};
|
|
1466
1824
|
ShapeStream.Replica = {
|
|
1467
1825
|
FULL: `full`,
|
|
1468
1826
|
DEFAULT: `default`
|
|
1469
1827
|
};
|
|
1828
|
+
function getSchemaFromHeaders(headers, options) {
|
|
1829
|
+
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
|
|
1830
|
+
if (!schemaHeader) {
|
|
1831
|
+
if ((options == null ? void 0 : options.required) && (options == null ? void 0 : options.url)) {
|
|
1832
|
+
throw new MissingHeadersError(options.url, [SHAPE_SCHEMA_HEADER]);
|
|
1833
|
+
}
|
|
1834
|
+
return {};
|
|
1835
|
+
}
|
|
1836
|
+
return JSON.parse(schemaHeader);
|
|
1837
|
+
}
|
|
1470
1838
|
function validateParams(params) {
|
|
1471
1839
|
if (!params) return;
|
|
1472
1840
|
const reservedParams = Object.keys(params).filter(
|
|
@@ -1731,9 +2099,13 @@ export {
|
|
|
1731
2099
|
FetchError,
|
|
1732
2100
|
Shape,
|
|
1733
2101
|
ShapeStream,
|
|
2102
|
+
camelToSnake,
|
|
2103
|
+
createColumnMapper,
|
|
1734
2104
|
isChangeMessage,
|
|
1735
2105
|
isControlMessage,
|
|
1736
2106
|
isVisibleInSnapshot,
|
|
1737
|
-
resolveValue
|
|
2107
|
+
resolveValue,
|
|
2108
|
+
snakeCamelMapper,
|
|
2109
|
+
snakeToCamel
|
|
1738
2110
|
};
|
|
1739
2111
|
//# sourceMappingURL=index.mjs.map
|