@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/cjs/index.cjs
CHANGED
|
@@ -90,10 +90,14 @@ __export(src_exports, {
|
|
|
90
90
|
FetchError: () => FetchError,
|
|
91
91
|
Shape: () => Shape,
|
|
92
92
|
ShapeStream: () => ShapeStream,
|
|
93
|
+
camelToSnake: () => camelToSnake,
|
|
94
|
+
createColumnMapper: () => createColumnMapper,
|
|
93
95
|
isChangeMessage: () => isChangeMessage,
|
|
94
96
|
isControlMessage: () => isControlMessage,
|
|
95
97
|
isVisibleInSnapshot: () => isVisibleInSnapshot,
|
|
96
|
-
resolveValue: () => resolveValue
|
|
98
|
+
resolveValue: () => resolveValue,
|
|
99
|
+
snakeCamelMapper: () => snakeCamelMapper,
|
|
100
|
+
snakeToCamel: () => snakeToCamel
|
|
97
101
|
});
|
|
98
102
|
module.exports = __toCommonJS(src_exports);
|
|
99
103
|
|
|
@@ -256,15 +260,37 @@ var MessageParser = class {
|
|
|
256
260
|
parse(messages, schema) {
|
|
257
261
|
return JSON.parse(messages, (key, value) => {
|
|
258
262
|
if ((key === `value` || key === `old_value`) && typeof value === `object` && value !== null) {
|
|
259
|
-
|
|
260
|
-
Object.keys(row).forEach((key2) => {
|
|
261
|
-
row[key2] = this.parseRow(key2, row[key2], schema);
|
|
262
|
-
});
|
|
263
|
-
if (this.transformer) value = this.transformer(value);
|
|
263
|
+
return this.transformMessageValue(value, schema);
|
|
264
264
|
}
|
|
265
265
|
return value;
|
|
266
266
|
});
|
|
267
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Parse an array of ChangeMessages from a snapshot response.
|
|
270
|
+
* Applies type parsing and transformations to the value and old_value properties.
|
|
271
|
+
*/
|
|
272
|
+
parseSnapshotData(messages, schema) {
|
|
273
|
+
return messages.map((message) => {
|
|
274
|
+
const msg = message;
|
|
275
|
+
if (msg.value && typeof msg.value === `object` && msg.value !== null) {
|
|
276
|
+
msg.value = this.transformMessageValue(msg.value, schema);
|
|
277
|
+
}
|
|
278
|
+
if (msg.old_value && typeof msg.old_value === `object` && msg.old_value !== null) {
|
|
279
|
+
msg.old_value = this.transformMessageValue(msg.old_value, schema);
|
|
280
|
+
}
|
|
281
|
+
return msg;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Transform a message value or old_value object by parsing its columns.
|
|
286
|
+
*/
|
|
287
|
+
transformMessageValue(value, schema) {
|
|
288
|
+
const row = value;
|
|
289
|
+
Object.keys(row).forEach((key) => {
|
|
290
|
+
row[key] = this.parseRow(key, row[key], schema);
|
|
291
|
+
});
|
|
292
|
+
return this.transformer ? this.transformer(row) : row;
|
|
293
|
+
}
|
|
268
294
|
// Parses the message values using the provided parser based on the schema information
|
|
269
295
|
parseRow(key, value, schema) {
|
|
270
296
|
var _b;
|
|
@@ -300,6 +326,151 @@ function makeNullableParser(parser, columnInfo, columnName) {
|
|
|
300
326
|
};
|
|
301
327
|
}
|
|
302
328
|
|
|
329
|
+
// src/column-mapper.ts
|
|
330
|
+
function snakeToCamel(str) {
|
|
331
|
+
var _a, _b, _c, _d;
|
|
332
|
+
const leadingUnderscores = (_b = (_a = str.match(/^_+/)) == null ? void 0 : _a[0]) != null ? _b : ``;
|
|
333
|
+
const withoutLeading = str.slice(leadingUnderscores.length);
|
|
334
|
+
const trailingUnderscores = (_d = (_c = withoutLeading.match(/_+$/)) == null ? void 0 : _c[0]) != null ? _d : ``;
|
|
335
|
+
const core = trailingUnderscores ? withoutLeading.slice(
|
|
336
|
+
0,
|
|
337
|
+
withoutLeading.length - trailingUnderscores.length
|
|
338
|
+
) : withoutLeading;
|
|
339
|
+
const normalized = core.toLowerCase();
|
|
340
|
+
const camelCased = normalized.replace(
|
|
341
|
+
/_+([a-z])/g,
|
|
342
|
+
(_, letter) => letter.toUpperCase()
|
|
343
|
+
);
|
|
344
|
+
return leadingUnderscores + camelCased + trailingUnderscores;
|
|
345
|
+
}
|
|
346
|
+
function camelToSnake(str) {
|
|
347
|
+
return str.replace(/([a-z])([A-Z])/g, `$1_$2`).replace(/([A-Z]+)([A-Z][a-z])/g, `$1_$2`).toLowerCase();
|
|
348
|
+
}
|
|
349
|
+
function createColumnMapper(mapping) {
|
|
350
|
+
const reverseMapping = {};
|
|
351
|
+
for (const [dbName, appName] of Object.entries(mapping)) {
|
|
352
|
+
reverseMapping[appName] = dbName;
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
decode: (dbColumnName) => {
|
|
356
|
+
var _a;
|
|
357
|
+
return (_a = mapping[dbColumnName]) != null ? _a : dbColumnName;
|
|
358
|
+
},
|
|
359
|
+
encode: (appColumnName) => {
|
|
360
|
+
var _a;
|
|
361
|
+
return (_a = reverseMapping[appColumnName]) != null ? _a : appColumnName;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function encodeWhereClause(whereClause, encode) {
|
|
366
|
+
if (!whereClause || !encode) return whereClause != null ? whereClause : ``;
|
|
367
|
+
const sqlKeywords = /* @__PURE__ */ new Set([
|
|
368
|
+
`SELECT`,
|
|
369
|
+
`FROM`,
|
|
370
|
+
`WHERE`,
|
|
371
|
+
`AND`,
|
|
372
|
+
`OR`,
|
|
373
|
+
`NOT`,
|
|
374
|
+
`IN`,
|
|
375
|
+
`IS`,
|
|
376
|
+
`NULL`,
|
|
377
|
+
`NULLS`,
|
|
378
|
+
`FIRST`,
|
|
379
|
+
`LAST`,
|
|
380
|
+
`TRUE`,
|
|
381
|
+
`FALSE`,
|
|
382
|
+
`LIKE`,
|
|
383
|
+
`ILIKE`,
|
|
384
|
+
`BETWEEN`,
|
|
385
|
+
`ASC`,
|
|
386
|
+
`DESC`,
|
|
387
|
+
`LIMIT`,
|
|
388
|
+
`OFFSET`,
|
|
389
|
+
`ORDER`,
|
|
390
|
+
`BY`,
|
|
391
|
+
`GROUP`,
|
|
392
|
+
`HAVING`,
|
|
393
|
+
`DISTINCT`,
|
|
394
|
+
`AS`,
|
|
395
|
+
`ON`,
|
|
396
|
+
`JOIN`,
|
|
397
|
+
`LEFT`,
|
|
398
|
+
`RIGHT`,
|
|
399
|
+
`INNER`,
|
|
400
|
+
`OUTER`,
|
|
401
|
+
`CROSS`,
|
|
402
|
+
`CASE`,
|
|
403
|
+
`WHEN`,
|
|
404
|
+
`THEN`,
|
|
405
|
+
`ELSE`,
|
|
406
|
+
`END`,
|
|
407
|
+
`CAST`,
|
|
408
|
+
`LOWER`,
|
|
409
|
+
`UPPER`,
|
|
410
|
+
`COALESCE`,
|
|
411
|
+
`NULLIF`
|
|
412
|
+
]);
|
|
413
|
+
const quotedRanges = [];
|
|
414
|
+
let pos = 0;
|
|
415
|
+
while (pos < whereClause.length) {
|
|
416
|
+
const ch = whereClause[pos];
|
|
417
|
+
if (ch === `'` || ch === `"`) {
|
|
418
|
+
const start = pos;
|
|
419
|
+
const quoteChar = ch;
|
|
420
|
+
pos++;
|
|
421
|
+
while (pos < whereClause.length) {
|
|
422
|
+
if (whereClause[pos] === quoteChar) {
|
|
423
|
+
if (whereClause[pos + 1] === quoteChar) {
|
|
424
|
+
pos += 2;
|
|
425
|
+
} else {
|
|
426
|
+
pos++;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
pos++;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
quotedRanges.push({ start, end: pos });
|
|
434
|
+
} else {
|
|
435
|
+
pos++;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const isInQuotedString = (pos2) => {
|
|
439
|
+
return quotedRanges.some((range) => pos2 >= range.start && pos2 < range.end);
|
|
440
|
+
};
|
|
441
|
+
const identifierPattern = new RegExp("(?<![a-zA-Z0-9_])([a-zA-Z_][a-zA-Z0-9_]*)(?![a-zA-Z0-9_])", "g");
|
|
442
|
+
return whereClause.replace(identifierPattern, (match, _p1, offset) => {
|
|
443
|
+
if (isInQuotedString(offset)) {
|
|
444
|
+
return match;
|
|
445
|
+
}
|
|
446
|
+
if (sqlKeywords.has(match.toUpperCase())) {
|
|
447
|
+
return match;
|
|
448
|
+
}
|
|
449
|
+
if (match.startsWith(`$`)) {
|
|
450
|
+
return match;
|
|
451
|
+
}
|
|
452
|
+
const encoded = encode(match);
|
|
453
|
+
return encoded;
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
function snakeCamelMapper(schema) {
|
|
457
|
+
if (schema) {
|
|
458
|
+
const mapping = {};
|
|
459
|
+
for (const dbColumn of Object.keys(schema)) {
|
|
460
|
+
mapping[dbColumn] = snakeToCamel(dbColumn);
|
|
461
|
+
}
|
|
462
|
+
return createColumnMapper(mapping);
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
decode: (dbColumnName) => {
|
|
466
|
+
return snakeToCamel(dbColumnName);
|
|
467
|
+
},
|
|
468
|
+
encode: (appColumnName) => {
|
|
469
|
+
return camelToSnake(appColumnName);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
303
474
|
// src/helpers.ts
|
|
304
475
|
function isChangeMessage(message) {
|
|
305
476
|
return `key` in message;
|
|
@@ -693,6 +864,127 @@ var ExpiredShapesCache = class {
|
|
|
693
864
|
};
|
|
694
865
|
var expiredShapesCache = new ExpiredShapesCache();
|
|
695
866
|
|
|
867
|
+
// src/up-to-date-tracker.ts
|
|
868
|
+
var UpToDateTracker = class {
|
|
869
|
+
constructor() {
|
|
870
|
+
this.data = {};
|
|
871
|
+
this.storageKey = `electric_up_to_date_tracker`;
|
|
872
|
+
this.cacheTTL = 6e4;
|
|
873
|
+
// 60s to match typical CDN s-maxage cache duration
|
|
874
|
+
this.maxEntries = 250;
|
|
875
|
+
this.writeThrottleMs = 6e4;
|
|
876
|
+
// Throttle localStorage writes to once per 60s
|
|
877
|
+
this.lastWriteTime = 0;
|
|
878
|
+
this.load();
|
|
879
|
+
this.cleanup();
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Records that a shape received an up-to-date message with a specific cursor.
|
|
883
|
+
* This timestamp and cursor are used to detect cache replay scenarios.
|
|
884
|
+
* Updates in-memory immediately, but throttles localStorage writes.
|
|
885
|
+
*/
|
|
886
|
+
recordUpToDate(shapeKey, cursor) {
|
|
887
|
+
this.data[shapeKey] = {
|
|
888
|
+
timestamp: Date.now(),
|
|
889
|
+
cursor
|
|
890
|
+
};
|
|
891
|
+
const keys = Object.keys(this.data);
|
|
892
|
+
if (keys.length > this.maxEntries) {
|
|
893
|
+
const oldest = keys.reduce(
|
|
894
|
+
(min, k) => this.data[k].timestamp < this.data[min].timestamp ? k : min
|
|
895
|
+
);
|
|
896
|
+
delete this.data[oldest];
|
|
897
|
+
}
|
|
898
|
+
this.scheduleSave();
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Schedules a throttled save to localStorage.
|
|
902
|
+
* Writes immediately if enough time has passed, otherwise schedules for later.
|
|
903
|
+
*/
|
|
904
|
+
scheduleSave() {
|
|
905
|
+
const now = Date.now();
|
|
906
|
+
const timeSinceLastWrite = now - this.lastWriteTime;
|
|
907
|
+
if (timeSinceLastWrite >= this.writeThrottleMs) {
|
|
908
|
+
this.lastWriteTime = now;
|
|
909
|
+
this.save();
|
|
910
|
+
} else if (!this.pendingSaveTimer) {
|
|
911
|
+
const delay = this.writeThrottleMs - timeSinceLastWrite;
|
|
912
|
+
this.pendingSaveTimer = setTimeout(() => {
|
|
913
|
+
this.lastWriteTime = Date.now();
|
|
914
|
+
this.pendingSaveTimer = void 0;
|
|
915
|
+
this.save();
|
|
916
|
+
}, delay);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Checks if we should enter replay mode for this shape.
|
|
921
|
+
* Returns the last seen cursor if there's a recent up-to-date (< 60s),
|
|
922
|
+
* which means we'll likely be replaying cached responses.
|
|
923
|
+
* Returns null if no recent up-to-date exists.
|
|
924
|
+
*/
|
|
925
|
+
shouldEnterReplayMode(shapeKey) {
|
|
926
|
+
const entry = this.data[shapeKey];
|
|
927
|
+
if (!entry) {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
const age = Date.now() - entry.timestamp;
|
|
931
|
+
if (age >= this.cacheTTL) {
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
return entry.cursor;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Cleans up expired entries from the cache.
|
|
938
|
+
* Called on initialization and can be called periodically.
|
|
939
|
+
*/
|
|
940
|
+
cleanup() {
|
|
941
|
+
const now = Date.now();
|
|
942
|
+
const keys = Object.keys(this.data);
|
|
943
|
+
let modified = false;
|
|
944
|
+
for (const key of keys) {
|
|
945
|
+
const age = now - this.data[key].timestamp;
|
|
946
|
+
if (age > this.cacheTTL) {
|
|
947
|
+
delete this.data[key];
|
|
948
|
+
modified = true;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (modified) {
|
|
952
|
+
this.save();
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
save() {
|
|
956
|
+
if (typeof localStorage === `undefined`) return;
|
|
957
|
+
try {
|
|
958
|
+
localStorage.setItem(this.storageKey, JSON.stringify(this.data));
|
|
959
|
+
} catch (e) {
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
load() {
|
|
963
|
+
if (typeof localStorage === `undefined`) return;
|
|
964
|
+
try {
|
|
965
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
966
|
+
if (stored) {
|
|
967
|
+
this.data = JSON.parse(stored);
|
|
968
|
+
}
|
|
969
|
+
} catch (e) {
|
|
970
|
+
this.data = {};
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Clears all tracked up-to-date timestamps.
|
|
975
|
+
* Useful for testing or manual cache invalidation.
|
|
976
|
+
*/
|
|
977
|
+
clear() {
|
|
978
|
+
this.data = {};
|
|
979
|
+
if (this.pendingSaveTimer) {
|
|
980
|
+
clearTimeout(this.pendingSaveTimer);
|
|
981
|
+
this.pendingSaveTimer = void 0;
|
|
982
|
+
}
|
|
983
|
+
this.save();
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
var upToDateTracker = new UpToDateTracker();
|
|
987
|
+
|
|
696
988
|
// src/snapshot-tracker.ts
|
|
697
989
|
var SnapshotTracker = class {
|
|
698
990
|
constructor() {
|
|
@@ -810,7 +1102,7 @@ function canonicalShapeKey(url) {
|
|
|
810
1102
|
cleanUrl.searchParams.sort();
|
|
811
1103
|
return cleanUrl.toString();
|
|
812
1104
|
}
|
|
813
|
-
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
|
|
1105
|
+
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;
|
|
814
1106
|
var ShapeStream = class {
|
|
815
1107
|
constructor(options) {
|
|
816
1108
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -845,6 +1137,10 @@ var ShapeStream = class {
|
|
|
845
1137
|
// counter for concurrent snapshot requests
|
|
846
1138
|
__privateAdd(this, _midStreamPromise);
|
|
847
1139
|
__privateAdd(this, _midStreamPromiseResolver);
|
|
1140
|
+
__privateAdd(this, _lastSeenCursor);
|
|
1141
|
+
// Last seen cursor from previous session (used to detect cached responses)
|
|
1142
|
+
__privateAdd(this, _currentFetchUrl);
|
|
1143
|
+
// Current fetch URL for computing shape key
|
|
848
1144
|
__privateAdd(this, _lastSseConnectionStartTime);
|
|
849
1145
|
__privateAdd(this, _minSseConnectionDuration, 1e3);
|
|
850
1146
|
// Minimum expected SSE connection duration (1 second)
|
|
@@ -863,10 +1159,21 @@ var ShapeStream = class {
|
|
|
863
1159
|
__privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
|
|
864
1160
|
__privateSet(this, _liveCacheBuster, ``);
|
|
865
1161
|
__privateSet(this, _shapeHandle, this.options.handle);
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1162
|
+
let transformer;
|
|
1163
|
+
if (options.columnMapper) {
|
|
1164
|
+
const applyColumnMapper = (row) => {
|
|
1165
|
+
const result = {};
|
|
1166
|
+
for (const [dbKey, value] of Object.entries(row)) {
|
|
1167
|
+
const appKey = options.columnMapper.decode(dbKey);
|
|
1168
|
+
result[appKey] = value;
|
|
1169
|
+
}
|
|
1170
|
+
return result;
|
|
1171
|
+
};
|
|
1172
|
+
transformer = options.transformer ? (row) => options.transformer(applyColumnMapper(row)) : applyColumnMapper;
|
|
1173
|
+
} else {
|
|
1174
|
+
transformer = options.transformer;
|
|
1175
|
+
}
|
|
1176
|
+
__privateSet(this, _messageParser, new MessageParser(options.parser, transformer));
|
|
870
1177
|
__privateSet(this, _onError, this.options.onError);
|
|
871
1178
|
__privateSet(this, _mode, (_b = this.options.log) != null ? _b : `full`);
|
|
872
1179
|
const baseFetchClient = (_c = options.fetchClient) != null ? _c : (...args) => fetch(...args);
|
|
@@ -957,7 +1264,7 @@ var ShapeStream = class {
|
|
|
957
1264
|
});
|
|
958
1265
|
}
|
|
959
1266
|
/**
|
|
960
|
-
* Request a snapshot for subset of data.
|
|
1267
|
+
* Request a snapshot for subset of data and inject it into the subscribed data stream.
|
|
961
1268
|
*
|
|
962
1269
|
* Only available when mode is `changes_only`.
|
|
963
1270
|
* Returns the insertion point & the data, but more importantly injects the data
|
|
@@ -984,8 +1291,7 @@ var ShapeStream = class {
|
|
|
984
1291
|
if (__privateGet(this, _activeSnapshotRequests) === 1) {
|
|
985
1292
|
__privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
|
|
986
1293
|
}
|
|
987
|
-
const {
|
|
988
|
-
const { metadata, data } = yield __privateMethod(this, _ShapeStream_instances, fetchSnapshot_fn).call(this, fetchUrl, requestHeaders);
|
|
1294
|
+
const { metadata, data } = yield this.fetchSnapshot(opts);
|
|
989
1295
|
const dataWithEndBoundary = data.concat([
|
|
990
1296
|
{ headers: __spreadValues({ control: `snapshot-end` }, metadata) }
|
|
991
1297
|
]);
|
|
@@ -1006,6 +1312,44 @@ var ShapeStream = class {
|
|
|
1006
1312
|
}
|
|
1007
1313
|
});
|
|
1008
1314
|
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Fetch a snapshot for subset of data.
|
|
1317
|
+
* Returns the metadata and the data, but does not inject it into the subscribed data stream.
|
|
1318
|
+
*
|
|
1319
|
+
* @param opts - The options for the snapshot request.
|
|
1320
|
+
* @returns The metadata and the data for the snapshot.
|
|
1321
|
+
*/
|
|
1322
|
+
fetchSnapshot(opts) {
|
|
1323
|
+
return __async(this, null, function* () {
|
|
1324
|
+
var _a;
|
|
1325
|
+
const { fetchUrl, requestHeaders } = yield __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
|
|
1326
|
+
const response = yield __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), {
|
|
1327
|
+
headers: requestHeaders
|
|
1328
|
+
});
|
|
1329
|
+
if (!response.ok) {
|
|
1330
|
+
throw new FetchError(
|
|
1331
|
+
response.status,
|
|
1332
|
+
void 0,
|
|
1333
|
+
void 0,
|
|
1334
|
+
Object.fromEntries([...response.headers.entries()]),
|
|
1335
|
+
fetchUrl.toString()
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
const schema = (_a = __privateGet(this, _schema)) != null ? _a : getSchemaFromHeaders(response.headers, {
|
|
1339
|
+
required: true,
|
|
1340
|
+
url: fetchUrl.toString()
|
|
1341
|
+
});
|
|
1342
|
+
const { metadata, data: rawData } = yield response.json();
|
|
1343
|
+
const data = __privateGet(this, _messageParser).parseSnapshotData(
|
|
1344
|
+
rawData,
|
|
1345
|
+
schema
|
|
1346
|
+
);
|
|
1347
|
+
return {
|
|
1348
|
+
metadata,
|
|
1349
|
+
data
|
|
1350
|
+
};
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1009
1353
|
};
|
|
1010
1354
|
_error = new WeakMap();
|
|
1011
1355
|
_fetchClient2 = new WeakMap();
|
|
@@ -1034,6 +1378,8 @@ _snapshotTracker = new WeakMap();
|
|
|
1034
1378
|
_activeSnapshotRequests = new WeakMap();
|
|
1035
1379
|
_midStreamPromise = new WeakMap();
|
|
1036
1380
|
_midStreamPromiseResolver = new WeakMap();
|
|
1381
|
+
_lastSeenCursor = new WeakMap();
|
|
1382
|
+
_currentFetchUrl = new WeakMap();
|
|
1037
1383
|
_lastSseConnectionStartTime = new WeakMap();
|
|
1038
1384
|
_minSseConnectionDuration = new WeakMap();
|
|
1039
1385
|
_consecutiveShortSseConnections = new WeakMap();
|
|
@@ -1043,6 +1389,9 @@ _sseBackoffBaseDelay = new WeakMap();
|
|
|
1043
1389
|
_sseBackoffMaxDelay = new WeakMap();
|
|
1044
1390
|
_unsubscribeFromVisibilityChanges = new WeakMap();
|
|
1045
1391
|
_ShapeStream_instances = new WeakSet();
|
|
1392
|
+
replayMode_get = function() {
|
|
1393
|
+
return __privateGet(this, _lastSeenCursor) !== void 0;
|
|
1394
|
+
};
|
|
1046
1395
|
start_fn = function() {
|
|
1047
1396
|
return __async(this, null, function* () {
|
|
1048
1397
|
var _a, _b, _c, _d, _e;
|
|
@@ -1142,6 +1491,7 @@ requestShape_fn = function() {
|
|
|
1142
1491
|
};
|
|
1143
1492
|
constructUrl_fn = function(url, resumingFromPause, subsetParams) {
|
|
1144
1493
|
return __async(this, null, function* () {
|
|
1494
|
+
var _a, _b, _c;
|
|
1145
1495
|
const [requestHeaders, params] = yield Promise.all([
|
|
1146
1496
|
resolveHeaders(this.options.headers),
|
|
1147
1497
|
this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
|
|
@@ -1150,7 +1500,13 @@ constructUrl_fn = function(url, resumingFromPause, subsetParams) {
|
|
|
1150
1500
|
const fetchUrl = new URL(url);
|
|
1151
1501
|
if (params) {
|
|
1152
1502
|
if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table);
|
|
1153
|
-
if (params.where
|
|
1503
|
+
if (params.where && typeof params.where === `string`) {
|
|
1504
|
+
const encodedWhere = encodeWhereClause(
|
|
1505
|
+
params.where,
|
|
1506
|
+
(_a = this.options.columnMapper) == null ? void 0 : _a.encode
|
|
1507
|
+
);
|
|
1508
|
+
setQueryParam(fetchUrl, WHERE_QUERY_PARAM, encodedWhere);
|
|
1509
|
+
}
|
|
1154
1510
|
if (params.columns)
|
|
1155
1511
|
setQueryParam(fetchUrl, COLUMNS_QUERY_PARAM, params.columns);
|
|
1156
1512
|
if (params.replica) setQueryParam(fetchUrl, REPLICA_PARAM, params.replica);
|
|
@@ -1167,20 +1523,34 @@ constructUrl_fn = function(url, resumingFromPause, subsetParams) {
|
|
|
1167
1523
|
}
|
|
1168
1524
|
}
|
|
1169
1525
|
if (subsetParams) {
|
|
1170
|
-
if (subsetParams.where)
|
|
1171
|
-
|
|
1526
|
+
if (subsetParams.where && typeof subsetParams.where === `string`) {
|
|
1527
|
+
const encodedWhere = encodeWhereClause(
|
|
1528
|
+
subsetParams.where,
|
|
1529
|
+
(_b = this.options.columnMapper) == null ? void 0 : _b.encode
|
|
1530
|
+
);
|
|
1531
|
+
setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, encodedWhere);
|
|
1532
|
+
}
|
|
1172
1533
|
if (subsetParams.params)
|
|
1173
|
-
|
|
1534
|
+
fetchUrl.searchParams.set(
|
|
1535
|
+
SUBSET_PARAM_WHERE_PARAMS,
|
|
1536
|
+
JSON.stringify(subsetParams.params)
|
|
1537
|
+
);
|
|
1174
1538
|
if (subsetParams.limit)
|
|
1175
1539
|
setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit);
|
|
1176
1540
|
if (subsetParams.offset)
|
|
1177
1541
|
setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset);
|
|
1178
|
-
if (subsetParams.orderBy)
|
|
1179
|
-
|
|
1542
|
+
if (subsetParams.orderBy && typeof subsetParams.orderBy === `string`) {
|
|
1543
|
+
const encodedOrderBy = encodeWhereClause(
|
|
1544
|
+
subsetParams.orderBy,
|
|
1545
|
+
(_c = this.options.columnMapper) == null ? void 0 : _c.encode
|
|
1546
|
+
);
|
|
1547
|
+
setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, encodedOrderBy);
|
|
1548
|
+
}
|
|
1180
1549
|
}
|
|
1181
1550
|
fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
|
|
1182
1551
|
fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
|
|
1183
|
-
|
|
1552
|
+
const isSnapshotRequest = subsetParams !== void 0;
|
|
1553
|
+
if (__privateGet(this, _isUpToDate) && !isSnapshotRequest) {
|
|
1184
1554
|
if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
|
|
1185
1555
|
fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
|
|
1186
1556
|
}
|
|
@@ -1237,11 +1607,7 @@ onInitialResponse_fn = function(response) {
|
|
|
1237
1607
|
if (liveCacheBuster) {
|
|
1238
1608
|
__privateSet(this, _liveCacheBuster, liveCacheBuster);
|
|
1239
1609
|
}
|
|
1240
|
-
|
|
1241
|
-
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
|
|
1242
|
-
return schemaHeader ? JSON.parse(schemaHeader) : {};
|
|
1243
|
-
};
|
|
1244
|
-
__privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchema());
|
|
1610
|
+
__privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchemaFromHeaders(headers));
|
|
1245
1611
|
if (status === 204) {
|
|
1246
1612
|
__privateSet(this, _lastSyncedAt, Date.now());
|
|
1247
1613
|
}
|
|
@@ -1264,6 +1630,17 @@ onMessages_fn = function(batch, isSseMessage = false) {
|
|
|
1264
1630
|
__privateSet(this, _isUpToDate, true);
|
|
1265
1631
|
__privateSet(this, _isMidStream, false);
|
|
1266
1632
|
(_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
|
|
1633
|
+
if (__privateGet(this, _ShapeStream_instances, replayMode_get) && !isSseMessage) {
|
|
1634
|
+
const currentCursor = __privateGet(this, _liveCacheBuster);
|
|
1635
|
+
if (currentCursor === __privateGet(this, _lastSeenCursor)) {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
__privateSet(this, _lastSeenCursor, void 0);
|
|
1640
|
+
if (__privateGet(this, _currentFetchUrl)) {
|
|
1641
|
+
const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
|
|
1642
|
+
upToDateTracker.recordUpToDate(shapeKey, __privateGet(this, _liveCacheBuster));
|
|
1643
|
+
}
|
|
1267
1644
|
}
|
|
1268
1645
|
const messagesToProcess = batch.filter((message) => {
|
|
1269
1646
|
if (isChangeMessage(message)) {
|
|
@@ -1278,6 +1655,14 @@ onMessages_fn = function(batch, isSseMessage = false) {
|
|
|
1278
1655
|
fetchShape_fn = function(opts) {
|
|
1279
1656
|
return __async(this, null, function* () {
|
|
1280
1657
|
var _a;
|
|
1658
|
+
__privateSet(this, _currentFetchUrl, opts.fetchUrl);
|
|
1659
|
+
if (!__privateGet(this, _isUpToDate) && !__privateGet(this, _ShapeStream_instances, replayMode_get)) {
|
|
1660
|
+
const shapeKey = canonicalShapeKey(opts.fetchUrl);
|
|
1661
|
+
const lastSeenCursor = upToDateTracker.shouldEnterReplayMode(shapeKey);
|
|
1662
|
+
if (lastSeenCursor) {
|
|
1663
|
+
__privateSet(this, _lastSeenCursor, lastSeenCursor);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1281
1666
|
const useSse = (_a = this.options.liveSse) != null ? _a : this.options.experimentalLiveSse;
|
|
1282
1667
|
if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling)) {
|
|
1283
1668
|
opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
|
|
@@ -1469,33 +1854,20 @@ reset_fn = function(handle) {
|
|
|
1469
1854
|
__privateSet(this, _consecutiveShortSseConnections, 0);
|
|
1470
1855
|
__privateSet(this, _sseFallbackToLongPolling, false);
|
|
1471
1856
|
};
|
|
1472
|
-
fetchSnapshot_fn = function(url, headers) {
|
|
1473
|
-
return __async(this, null, function* () {
|
|
1474
|
-
const response = yield __privateGet(this, _fetchClient2).call(this, url.toString(), { headers });
|
|
1475
|
-
if (!response.ok) {
|
|
1476
|
-
throw new FetchError(
|
|
1477
|
-
response.status,
|
|
1478
|
-
void 0,
|
|
1479
|
-
void 0,
|
|
1480
|
-
Object.fromEntries([...response.headers.entries()]),
|
|
1481
|
-
url.toString()
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
const { metadata, data } = yield response.json();
|
|
1485
|
-
const batch = __privateGet(this, _messageParser).parse(
|
|
1486
|
-
JSON.stringify(data),
|
|
1487
|
-
__privateGet(this, _schema)
|
|
1488
|
-
);
|
|
1489
|
-
return {
|
|
1490
|
-
metadata,
|
|
1491
|
-
data: batch
|
|
1492
|
-
};
|
|
1493
|
-
});
|
|
1494
|
-
};
|
|
1495
1857
|
ShapeStream.Replica = {
|
|
1496
1858
|
FULL: `full`,
|
|
1497
1859
|
DEFAULT: `default`
|
|
1498
1860
|
};
|
|
1861
|
+
function getSchemaFromHeaders(headers, options) {
|
|
1862
|
+
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
|
|
1863
|
+
if (!schemaHeader) {
|
|
1864
|
+
if ((options == null ? void 0 : options.required) && (options == null ? void 0 : options.url)) {
|
|
1865
|
+
throw new MissingHeadersError(options.url, [SHAPE_SCHEMA_HEADER]);
|
|
1866
|
+
}
|
|
1867
|
+
return {};
|
|
1868
|
+
}
|
|
1869
|
+
return JSON.parse(schemaHeader);
|
|
1870
|
+
}
|
|
1499
1871
|
function validateParams(params) {
|
|
1500
1872
|
if (!params) return;
|
|
1501
1873
|
const reservedParams = Object.keys(params).filter(
|
|
@@ -1761,9 +2133,13 @@ notify_fn = function() {
|
|
|
1761
2133
|
FetchError,
|
|
1762
2134
|
Shape,
|
|
1763
2135
|
ShapeStream,
|
|
2136
|
+
camelToSnake,
|
|
2137
|
+
createColumnMapper,
|
|
1764
2138
|
isChangeMessage,
|
|
1765
2139
|
isControlMessage,
|
|
1766
2140
|
isVisibleInSnapshot,
|
|
1767
|
-
resolveValue
|
|
2141
|
+
resolveValue,
|
|
2142
|
+
snakeCamelMapper,
|
|
2143
|
+
snakeToCamel
|
|
1768
2144
|
});
|
|
1769
2145
|
//# sourceMappingURL=index.cjs.map
|