@durable-streams/client 0.1.2 → 0.1.3
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/README.md +7 -1
- package/dist/index.cjs +79 -2
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +77 -2
- package/package.json +2 -2
- package/src/index.ts +3 -0
- package/src/sse.ts +3 -0
- package/src/stream.ts +8 -1
- package/src/types.ts +18 -0
- package/src/utils.ts +120 -0
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -466,6 +466,7 @@ async function* parseSSEStream(stream$1, signal) {
|
|
|
466
466
|
const { done, value } = await reader.read();
|
|
467
467
|
if (done) break;
|
|
468
468
|
buffer += decoder.decode(value, { stream: true });
|
|
469
|
+
buffer = buffer.replace(/\r\n/g, `\n`).replace(/\r/g, `\n`);
|
|
469
470
|
const lines = buffer.split(`\n`);
|
|
470
471
|
buffer = lines.pop() ?? ``;
|
|
471
472
|
for (const line of lines) if (line === ``) {
|
|
@@ -1180,6 +1181,78 @@ async function resolveParams(params) {
|
|
|
1180
1181
|
else resolved[key] = value;
|
|
1181
1182
|
return resolved;
|
|
1182
1183
|
}
|
|
1184
|
+
const warnedOrigins = new Set();
|
|
1185
|
+
/**
|
|
1186
|
+
* Safely read NODE_ENV without triggering "process is not defined" errors.
|
|
1187
|
+
* Works in both browser and Node.js environments.
|
|
1188
|
+
*/
|
|
1189
|
+
function getNodeEnvSafely() {
|
|
1190
|
+
if (typeof process === `undefined`) return void 0;
|
|
1191
|
+
return process.env?.NODE_ENV;
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Check if we're in a browser environment.
|
|
1195
|
+
*/
|
|
1196
|
+
function isBrowserEnvironment() {
|
|
1197
|
+
return typeof globalThis.window !== `undefined`;
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Get window.location.href safely, returning undefined if not available.
|
|
1201
|
+
*/
|
|
1202
|
+
function getWindowLocationHref() {
|
|
1203
|
+
if (typeof globalThis.window !== `undefined` && typeof globalThis.window.location !== `undefined`) return globalThis.window.location.href;
|
|
1204
|
+
return void 0;
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Resolve a URL string, handling relative URLs in browser environments.
|
|
1208
|
+
* Returns undefined if the URL cannot be parsed.
|
|
1209
|
+
*/
|
|
1210
|
+
function resolveUrlMaybe(urlString) {
|
|
1211
|
+
try {
|
|
1212
|
+
return new URL(urlString);
|
|
1213
|
+
} catch {
|
|
1214
|
+
const base = getWindowLocationHref();
|
|
1215
|
+
if (base) try {
|
|
1216
|
+
return new URL(urlString, base);
|
|
1217
|
+
} catch {
|
|
1218
|
+
return void 0;
|
|
1219
|
+
}
|
|
1220
|
+
return void 0;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Warn if using HTTP (not HTTPS) URL in a browser environment.
|
|
1225
|
+
* HTTP typically limits browsers to ~6 concurrent connections per origin under HTTP/1.1,
|
|
1226
|
+
* which can cause slow streams and app freezes with multiple active streams.
|
|
1227
|
+
*
|
|
1228
|
+
* Features:
|
|
1229
|
+
* - Warns only once per origin to prevent log spam
|
|
1230
|
+
* - Handles relative URLs by resolving against window.location.href
|
|
1231
|
+
* - Safe to call in Node.js environments (no-op)
|
|
1232
|
+
* - Skips warning during tests (NODE_ENV=test)
|
|
1233
|
+
*/
|
|
1234
|
+
function warnIfUsingHttpInBrowser(url, warnOnHttp) {
|
|
1235
|
+
if (warnOnHttp === false) return;
|
|
1236
|
+
const nodeEnv = getNodeEnvSafely();
|
|
1237
|
+
if (nodeEnv === `test`) return;
|
|
1238
|
+
if (!isBrowserEnvironment() || typeof console === `undefined` || typeof console.warn !== `function`) return;
|
|
1239
|
+
const urlStr = url instanceof URL ? url.toString() : url;
|
|
1240
|
+
const parsedUrl = resolveUrlMaybe(urlStr);
|
|
1241
|
+
if (!parsedUrl) return;
|
|
1242
|
+
if (parsedUrl.protocol === `http:`) {
|
|
1243
|
+
if (!warnedOrigins.has(parsedUrl.origin)) {
|
|
1244
|
+
warnedOrigins.add(parsedUrl.origin);
|
|
1245
|
+
console.warn("[DurableStream] Using HTTP (not HTTPS) typically limits browsers to ~6 concurrent connections per origin under HTTP/1.1. This can cause slow streams and app freezes with multiple active streams. Use HTTPS for HTTP/2 support. See https://electric-sql.com/r/electric-http2 for more information.");
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Reset the HTTP warning state. Only exported for testing purposes.
|
|
1251
|
+
* @internal
|
|
1252
|
+
*/
|
|
1253
|
+
function _resetHttpWarningForTesting() {
|
|
1254
|
+
warnedOrigins.clear();
|
|
1255
|
+
}
|
|
1183
1256
|
|
|
1184
1257
|
//#endregion
|
|
1185
1258
|
//#region src/stream-api.ts
|
|
@@ -1787,7 +1860,8 @@ var DurableStream = class DurableStream {
|
|
|
1787
1860
|
offset: options?.offset,
|
|
1788
1861
|
live: options?.live,
|
|
1789
1862
|
json: options?.json,
|
|
1790
|
-
onError: options?.onError ?? this.#onError
|
|
1863
|
+
onError: options?.onError ?? this.#onError,
|
|
1864
|
+
warnOnHttp: options?.warnOnHttp ?? this.#options.warnOnHttp
|
|
1791
1865
|
});
|
|
1792
1866
|
}
|
|
1793
1867
|
/**
|
|
@@ -1848,6 +1922,7 @@ function toReadableStream(source) {
|
|
|
1848
1922
|
function validateOptions(options) {
|
|
1849
1923
|
if (!options.url) throw new MissingStreamUrlError();
|
|
1850
1924
|
if (options.signal && !(options.signal instanceof AbortSignal)) throw new InvalidSignalError();
|
|
1925
|
+
warnIfUsingHttpInBrowser(options.url, options.warnOnHttp);
|
|
1851
1926
|
}
|
|
1852
1927
|
|
|
1853
1928
|
//#endregion
|
|
@@ -1869,7 +1944,9 @@ exports.STREAM_OFFSET_HEADER = STREAM_OFFSET_HEADER
|
|
|
1869
1944
|
exports.STREAM_SEQ_HEADER = STREAM_SEQ_HEADER
|
|
1870
1945
|
exports.STREAM_TTL_HEADER = STREAM_TTL_HEADER
|
|
1871
1946
|
exports.STREAM_UP_TO_DATE_HEADER = STREAM_UP_TO_DATE_HEADER
|
|
1947
|
+
exports._resetHttpWarningForTesting = _resetHttpWarningForTesting
|
|
1872
1948
|
exports.asAsyncIterableReadableStream = asAsyncIterableReadableStream
|
|
1873
1949
|
exports.createFetchWithBackoff = createFetchWithBackoff
|
|
1874
1950
|
exports.createFetchWithConsumedBody = createFetchWithConsumedBody
|
|
1875
|
-
exports.stream = stream
|
|
1951
|
+
exports.stream = stream
|
|
1952
|
+
exports.warnIfUsingHttpInBrowser = warnIfUsingHttpInBrowser
|
package/dist/index.d.cts
CHANGED
|
@@ -249,6 +249,14 @@ interface StreamOptions {
|
|
|
249
249
|
* fall back to long-polling mode.
|
|
250
250
|
*/
|
|
251
251
|
sseResilience?: SSEResilienceOptions;
|
|
252
|
+
/**
|
|
253
|
+
* Whether to warn when using HTTP (not HTTPS) URLs in browser environments.
|
|
254
|
+
* HTTP limits browsers to 6 concurrent connections (HTTP/1.1), which can
|
|
255
|
+
* cause slow streams and app freezes with multiple active streams.
|
|
256
|
+
*
|
|
257
|
+
* @default true
|
|
258
|
+
*/
|
|
259
|
+
warnOnHttp?: boolean;
|
|
252
260
|
}
|
|
253
261
|
/**
|
|
254
262
|
* Options for SSE connection resilience.
|
|
@@ -374,6 +382,14 @@ interface StreamHandleOptions {
|
|
|
374
382
|
* @default true
|
|
375
383
|
*/
|
|
376
384
|
batching?: boolean;
|
|
385
|
+
/**
|
|
386
|
+
* Whether to warn when using HTTP (not HTTPS) URLs in browser environments.
|
|
387
|
+
* HTTP limits browsers to 6 concurrent connections (HTTP/1.1), which can
|
|
388
|
+
* cause slow streams and app freezes with multiple active streams.
|
|
389
|
+
*
|
|
390
|
+
* @default true
|
|
391
|
+
*/
|
|
392
|
+
warnOnHttp?: boolean;
|
|
377
393
|
}
|
|
378
394
|
/**
|
|
379
395
|
* Options for creating a new stream.
|
|
@@ -944,6 +960,26 @@ declare class DurableStream {
|
|
|
944
960
|
stream<TJson = unknown>(options?: Omit<StreamOptions, `url`>): Promise<StreamResponse<TJson>>;
|
|
945
961
|
}
|
|
946
962
|
|
|
963
|
+
//#endregion
|
|
964
|
+
//#region src/utils.d.ts
|
|
965
|
+
/**
|
|
966
|
+
* Warn if using HTTP (not HTTPS) URL in a browser environment.
|
|
967
|
+
* HTTP typically limits browsers to ~6 concurrent connections per origin under HTTP/1.1,
|
|
968
|
+
* which can cause slow streams and app freezes with multiple active streams.
|
|
969
|
+
*
|
|
970
|
+
* Features:
|
|
971
|
+
* - Warns only once per origin to prevent log spam
|
|
972
|
+
* - Handles relative URLs by resolving against window.location.href
|
|
973
|
+
* - Safe to call in Node.js environments (no-op)
|
|
974
|
+
* - Skips warning during tests (NODE_ENV=test)
|
|
975
|
+
*/
|
|
976
|
+
declare function warnIfUsingHttpInBrowser(url: string | URL, warnOnHttp?: boolean): void;
|
|
977
|
+
/**
|
|
978
|
+
* Reset the HTTP warning state. Only exported for testing purposes.
|
|
979
|
+
* @internal
|
|
980
|
+
*/
|
|
981
|
+
declare function _resetHttpWarningForTesting(): void;
|
|
982
|
+
|
|
947
983
|
//#endregion
|
|
948
984
|
//#region src/error.d.ts
|
|
949
985
|
/**
|
|
@@ -1069,4 +1105,4 @@ declare const SSE_COMPATIBLE_CONTENT_TYPES: ReadonlyArray<string>;
|
|
|
1069
1105
|
declare const DURABLE_STREAM_PROTOCOL_QUERY_PARAMS: Array<string>;
|
|
1070
1106
|
|
|
1071
1107
|
//#endregion
|
|
1072
|
-
export { AppendOptions, BackoffDefaults, BackoffOptions, ByteChunk, CURSOR_QUERY_PARAM, CreateOptions, DURABLE_STREAM_PROTOCOL_QUERY_PARAMS, DurableStream, DurableStreamError, DurableStreamErrorCode, DurableStreamOptions, FetchBackoffAbortError, FetchError, HeadResult, HeadersRecord, InvalidSignalError, JsonBatch, JsonBatchMeta, LIVE_QUERY_PARAM, LegacyLiveMode, LiveMode, MaybePromise, MissingStreamUrlError, OFFSET_QUERY_PARAM, Offset, ParamsRecord, ReadOptions, ReadableStreamAsyncIterable, RetryOpts, SSEResilienceOptions, SSE_COMPATIBLE_CONTENT_TYPES, STREAM_CURSOR_HEADER, STREAM_EXPIRES_AT_HEADER, STREAM_OFFSET_HEADER, STREAM_SEQ_HEADER, STREAM_TTL_HEADER, STREAM_UP_TO_DATE_HEADER, StreamErrorHandler, StreamHandleOptions, StreamOptions, StreamResponse, TextChunk, asAsyncIterableReadableStream, createFetchWithBackoff, createFetchWithConsumedBody, stream };
|
|
1108
|
+
export { AppendOptions, BackoffDefaults, BackoffOptions, ByteChunk, CURSOR_QUERY_PARAM, CreateOptions, DURABLE_STREAM_PROTOCOL_QUERY_PARAMS, DurableStream, DurableStreamError, DurableStreamErrorCode, DurableStreamOptions, FetchBackoffAbortError, FetchError, HeadResult, HeadersRecord, InvalidSignalError, JsonBatch, JsonBatchMeta, LIVE_QUERY_PARAM, LegacyLiveMode, LiveMode, MaybePromise, MissingStreamUrlError, OFFSET_QUERY_PARAM, Offset, ParamsRecord, ReadOptions, ReadableStreamAsyncIterable, RetryOpts, SSEResilienceOptions, SSE_COMPATIBLE_CONTENT_TYPES, STREAM_CURSOR_HEADER, STREAM_EXPIRES_AT_HEADER, STREAM_OFFSET_HEADER, STREAM_SEQ_HEADER, STREAM_TTL_HEADER, STREAM_UP_TO_DATE_HEADER, StreamErrorHandler, StreamHandleOptions, StreamOptions, StreamResponse, TextChunk, _resetHttpWarningForTesting, asAsyncIterableReadableStream, createFetchWithBackoff, createFetchWithConsumedBody, stream, warnIfUsingHttpInBrowser };
|
package/dist/index.d.ts
CHANGED
|
@@ -249,6 +249,14 @@ interface StreamOptions {
|
|
|
249
249
|
* fall back to long-polling mode.
|
|
250
250
|
*/
|
|
251
251
|
sseResilience?: SSEResilienceOptions;
|
|
252
|
+
/**
|
|
253
|
+
* Whether to warn when using HTTP (not HTTPS) URLs in browser environments.
|
|
254
|
+
* HTTP limits browsers to 6 concurrent connections (HTTP/1.1), which can
|
|
255
|
+
* cause slow streams and app freezes with multiple active streams.
|
|
256
|
+
*
|
|
257
|
+
* @default true
|
|
258
|
+
*/
|
|
259
|
+
warnOnHttp?: boolean;
|
|
252
260
|
}
|
|
253
261
|
/**
|
|
254
262
|
* Options for SSE connection resilience.
|
|
@@ -374,6 +382,14 @@ interface StreamHandleOptions {
|
|
|
374
382
|
* @default true
|
|
375
383
|
*/
|
|
376
384
|
batching?: boolean;
|
|
385
|
+
/**
|
|
386
|
+
* Whether to warn when using HTTP (not HTTPS) URLs in browser environments.
|
|
387
|
+
* HTTP limits browsers to 6 concurrent connections (HTTP/1.1), which can
|
|
388
|
+
* cause slow streams and app freezes with multiple active streams.
|
|
389
|
+
*
|
|
390
|
+
* @default true
|
|
391
|
+
*/
|
|
392
|
+
warnOnHttp?: boolean;
|
|
377
393
|
}
|
|
378
394
|
/**
|
|
379
395
|
* Options for creating a new stream.
|
|
@@ -944,6 +960,26 @@ declare class DurableStream {
|
|
|
944
960
|
stream<TJson = unknown>(options?: Omit<StreamOptions, `url`>): Promise<StreamResponse<TJson>>;
|
|
945
961
|
}
|
|
946
962
|
|
|
963
|
+
//#endregion
|
|
964
|
+
//#region src/utils.d.ts
|
|
965
|
+
/**
|
|
966
|
+
* Warn if using HTTP (not HTTPS) URL in a browser environment.
|
|
967
|
+
* HTTP typically limits browsers to ~6 concurrent connections per origin under HTTP/1.1,
|
|
968
|
+
* which can cause slow streams and app freezes with multiple active streams.
|
|
969
|
+
*
|
|
970
|
+
* Features:
|
|
971
|
+
* - Warns only once per origin to prevent log spam
|
|
972
|
+
* - Handles relative URLs by resolving against window.location.href
|
|
973
|
+
* - Safe to call in Node.js environments (no-op)
|
|
974
|
+
* - Skips warning during tests (NODE_ENV=test)
|
|
975
|
+
*/
|
|
976
|
+
declare function warnIfUsingHttpInBrowser(url: string | URL, warnOnHttp?: boolean): void;
|
|
977
|
+
/**
|
|
978
|
+
* Reset the HTTP warning state. Only exported for testing purposes.
|
|
979
|
+
* @internal
|
|
980
|
+
*/
|
|
981
|
+
declare function _resetHttpWarningForTesting(): void;
|
|
982
|
+
|
|
947
983
|
//#endregion
|
|
948
984
|
//#region src/error.d.ts
|
|
949
985
|
/**
|
|
@@ -1069,4 +1105,4 @@ declare const SSE_COMPATIBLE_CONTENT_TYPES: ReadonlyArray<string>;
|
|
|
1069
1105
|
declare const DURABLE_STREAM_PROTOCOL_QUERY_PARAMS: Array<string>;
|
|
1070
1106
|
|
|
1071
1107
|
//#endregion
|
|
1072
|
-
export { AppendOptions, BackoffDefaults, BackoffOptions, ByteChunk, CURSOR_QUERY_PARAM, CreateOptions, DURABLE_STREAM_PROTOCOL_QUERY_PARAMS, DurableStream, DurableStreamError, DurableStreamErrorCode, DurableStreamOptions, FetchBackoffAbortError, FetchError, HeadResult, HeadersRecord, InvalidSignalError, JsonBatch, JsonBatchMeta, LIVE_QUERY_PARAM, LegacyLiveMode, LiveMode, MaybePromise, MissingStreamUrlError, OFFSET_QUERY_PARAM, Offset, ParamsRecord, ReadOptions, ReadableStreamAsyncIterable, RetryOpts, SSEResilienceOptions, SSE_COMPATIBLE_CONTENT_TYPES, STREAM_CURSOR_HEADER, STREAM_EXPIRES_AT_HEADER, STREAM_OFFSET_HEADER, STREAM_SEQ_HEADER, STREAM_TTL_HEADER, STREAM_UP_TO_DATE_HEADER, StreamErrorHandler, StreamHandleOptions, StreamOptions, StreamResponse, TextChunk, asAsyncIterableReadableStream, createFetchWithBackoff, createFetchWithConsumedBody, stream };
|
|
1108
|
+
export { AppendOptions, BackoffDefaults, BackoffOptions, ByteChunk, CURSOR_QUERY_PARAM, CreateOptions, DURABLE_STREAM_PROTOCOL_QUERY_PARAMS, DurableStream, DurableStreamError, DurableStreamErrorCode, DurableStreamOptions, FetchBackoffAbortError, FetchError, HeadResult, HeadersRecord, InvalidSignalError, JsonBatch, JsonBatchMeta, LIVE_QUERY_PARAM, LegacyLiveMode, LiveMode, MaybePromise, MissingStreamUrlError, OFFSET_QUERY_PARAM, Offset, ParamsRecord, ReadOptions, ReadableStreamAsyncIterable, RetryOpts, SSEResilienceOptions, SSE_COMPATIBLE_CONTENT_TYPES, STREAM_CURSOR_HEADER, STREAM_EXPIRES_AT_HEADER, STREAM_OFFSET_HEADER, STREAM_SEQ_HEADER, STREAM_TTL_HEADER, STREAM_UP_TO_DATE_HEADER, StreamErrorHandler, StreamHandleOptions, StreamOptions, StreamResponse, TextChunk, _resetHttpWarningForTesting, asAsyncIterableReadableStream, createFetchWithBackoff, createFetchWithConsumedBody, stream, warnIfUsingHttpInBrowser };
|
package/dist/index.js
CHANGED
|
@@ -442,6 +442,7 @@ async function* parseSSEStream(stream$1, signal) {
|
|
|
442
442
|
const { done, value } = await reader.read();
|
|
443
443
|
if (done) break;
|
|
444
444
|
buffer += decoder.decode(value, { stream: true });
|
|
445
|
+
buffer = buffer.replace(/\r\n/g, `\n`).replace(/\r/g, `\n`);
|
|
445
446
|
const lines = buffer.split(`\n`);
|
|
446
447
|
buffer = lines.pop() ?? ``;
|
|
447
448
|
for (const line of lines) if (line === ``) {
|
|
@@ -1156,6 +1157,78 @@ async function resolveParams(params) {
|
|
|
1156
1157
|
else resolved[key] = value;
|
|
1157
1158
|
return resolved;
|
|
1158
1159
|
}
|
|
1160
|
+
const warnedOrigins = new Set();
|
|
1161
|
+
/**
|
|
1162
|
+
* Safely read NODE_ENV without triggering "process is not defined" errors.
|
|
1163
|
+
* Works in both browser and Node.js environments.
|
|
1164
|
+
*/
|
|
1165
|
+
function getNodeEnvSafely() {
|
|
1166
|
+
if (typeof process === `undefined`) return void 0;
|
|
1167
|
+
return process.env?.NODE_ENV;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Check if we're in a browser environment.
|
|
1171
|
+
*/
|
|
1172
|
+
function isBrowserEnvironment() {
|
|
1173
|
+
return typeof globalThis.window !== `undefined`;
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Get window.location.href safely, returning undefined if not available.
|
|
1177
|
+
*/
|
|
1178
|
+
function getWindowLocationHref() {
|
|
1179
|
+
if (typeof globalThis.window !== `undefined` && typeof globalThis.window.location !== `undefined`) return globalThis.window.location.href;
|
|
1180
|
+
return void 0;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Resolve a URL string, handling relative URLs in browser environments.
|
|
1184
|
+
* Returns undefined if the URL cannot be parsed.
|
|
1185
|
+
*/
|
|
1186
|
+
function resolveUrlMaybe(urlString) {
|
|
1187
|
+
try {
|
|
1188
|
+
return new URL(urlString);
|
|
1189
|
+
} catch {
|
|
1190
|
+
const base = getWindowLocationHref();
|
|
1191
|
+
if (base) try {
|
|
1192
|
+
return new URL(urlString, base);
|
|
1193
|
+
} catch {
|
|
1194
|
+
return void 0;
|
|
1195
|
+
}
|
|
1196
|
+
return void 0;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Warn if using HTTP (not HTTPS) URL in a browser environment.
|
|
1201
|
+
* HTTP typically limits browsers to ~6 concurrent connections per origin under HTTP/1.1,
|
|
1202
|
+
* which can cause slow streams and app freezes with multiple active streams.
|
|
1203
|
+
*
|
|
1204
|
+
* Features:
|
|
1205
|
+
* - Warns only once per origin to prevent log spam
|
|
1206
|
+
* - Handles relative URLs by resolving against window.location.href
|
|
1207
|
+
* - Safe to call in Node.js environments (no-op)
|
|
1208
|
+
* - Skips warning during tests (NODE_ENV=test)
|
|
1209
|
+
*/
|
|
1210
|
+
function warnIfUsingHttpInBrowser(url, warnOnHttp) {
|
|
1211
|
+
if (warnOnHttp === false) return;
|
|
1212
|
+
const nodeEnv = getNodeEnvSafely();
|
|
1213
|
+
if (nodeEnv === `test`) return;
|
|
1214
|
+
if (!isBrowserEnvironment() || typeof console === `undefined` || typeof console.warn !== `function`) return;
|
|
1215
|
+
const urlStr = url instanceof URL ? url.toString() : url;
|
|
1216
|
+
const parsedUrl = resolveUrlMaybe(urlStr);
|
|
1217
|
+
if (!parsedUrl) return;
|
|
1218
|
+
if (parsedUrl.protocol === `http:`) {
|
|
1219
|
+
if (!warnedOrigins.has(parsedUrl.origin)) {
|
|
1220
|
+
warnedOrigins.add(parsedUrl.origin);
|
|
1221
|
+
console.warn("[DurableStream] Using HTTP (not HTTPS) typically limits browsers to ~6 concurrent connections per origin under HTTP/1.1. This can cause slow streams and app freezes with multiple active streams. Use HTTPS for HTTP/2 support. See https://electric-sql.com/r/electric-http2 for more information.");
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Reset the HTTP warning state. Only exported for testing purposes.
|
|
1227
|
+
* @internal
|
|
1228
|
+
*/
|
|
1229
|
+
function _resetHttpWarningForTesting() {
|
|
1230
|
+
warnedOrigins.clear();
|
|
1231
|
+
}
|
|
1159
1232
|
|
|
1160
1233
|
//#endregion
|
|
1161
1234
|
//#region src/stream-api.ts
|
|
@@ -1763,7 +1836,8 @@ var DurableStream = class DurableStream {
|
|
|
1763
1836
|
offset: options?.offset,
|
|
1764
1837
|
live: options?.live,
|
|
1765
1838
|
json: options?.json,
|
|
1766
|
-
onError: options?.onError ?? this.#onError
|
|
1839
|
+
onError: options?.onError ?? this.#onError,
|
|
1840
|
+
warnOnHttp: options?.warnOnHttp ?? this.#options.warnOnHttp
|
|
1767
1841
|
});
|
|
1768
1842
|
}
|
|
1769
1843
|
/**
|
|
@@ -1824,7 +1898,8 @@ function toReadableStream(source) {
|
|
|
1824
1898
|
function validateOptions(options) {
|
|
1825
1899
|
if (!options.url) throw new MissingStreamUrlError();
|
|
1826
1900
|
if (options.signal && !(options.signal instanceof AbortSignal)) throw new InvalidSignalError();
|
|
1901
|
+
warnIfUsingHttpInBrowser(options.url, options.warnOnHttp);
|
|
1827
1902
|
}
|
|
1828
1903
|
|
|
1829
1904
|
//#endregion
|
|
1830
|
-
export { BackoffDefaults, CURSOR_QUERY_PARAM, DURABLE_STREAM_PROTOCOL_QUERY_PARAMS, DurableStream, DurableStreamError, FetchBackoffAbortError, FetchError, InvalidSignalError, LIVE_QUERY_PARAM, MissingStreamUrlError, OFFSET_QUERY_PARAM, SSE_COMPATIBLE_CONTENT_TYPES, STREAM_CURSOR_HEADER, STREAM_EXPIRES_AT_HEADER, STREAM_OFFSET_HEADER, STREAM_SEQ_HEADER, STREAM_TTL_HEADER, STREAM_UP_TO_DATE_HEADER, asAsyncIterableReadableStream, createFetchWithBackoff, createFetchWithConsumedBody, stream };
|
|
1905
|
+
export { BackoffDefaults, CURSOR_QUERY_PARAM, DURABLE_STREAM_PROTOCOL_QUERY_PARAMS, DurableStream, DurableStreamError, FetchBackoffAbortError, FetchError, InvalidSignalError, LIVE_QUERY_PARAM, MissingStreamUrlError, OFFSET_QUERY_PARAM, SSE_COMPATIBLE_CONTENT_TYPES, STREAM_CURSOR_HEADER, STREAM_EXPIRES_AT_HEADER, STREAM_OFFSET_HEADER, STREAM_SEQ_HEADER, STREAM_TTL_HEADER, STREAM_UP_TO_DATE_HEADER, _resetHttpWarningForTesting, asAsyncIterableReadableStream, createFetchWithBackoff, createFetchWithConsumedBody, stream, warnIfUsingHttpInBrowser };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@durable-streams/client",
|
|
3
3
|
"description": "TypeScript client for the Durable Streams protocol",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"author": "Durable Stream contributors",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"fast-check": "^4.4.0",
|
|
49
49
|
"tsdown": "^0.9.0",
|
|
50
|
-
"@durable-streams/server": "0.1.
|
|
50
|
+
"@durable-streams/server": "0.1.4"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=18.0.0"
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,9 @@ export { stream } from "./stream-api"
|
|
|
20
20
|
// DurableStream class for read/write operations
|
|
21
21
|
export { DurableStream, type DurableStreamOptions } from "./stream"
|
|
22
22
|
|
|
23
|
+
// HTTP warning utility
|
|
24
|
+
export { warnIfUsingHttpInBrowser, _resetHttpWarningForTesting } from "./utils"
|
|
25
|
+
|
|
23
26
|
// ============================================================================
|
|
24
27
|
// Types
|
|
25
28
|
// ============================================================================
|
package/src/sse.ts
CHANGED
|
@@ -50,6 +50,9 @@ export async function* parseSSEStream(
|
|
|
50
50
|
|
|
51
51
|
buffer += decoder.decode(value, { stream: true })
|
|
52
52
|
|
|
53
|
+
// Normalize line endings: CRLF → LF, lone CR → LF (per SSE spec)
|
|
54
|
+
buffer = buffer.replace(/\r\n/g, `\n`).replace(/\r/g, `\n`)
|
|
55
|
+
|
|
53
56
|
// Process complete lines
|
|
54
57
|
const lines = buffer.split(`\n`)
|
|
55
58
|
// Keep the last incomplete line in the buffer
|
package/src/stream.ts
CHANGED
|
@@ -24,7 +24,12 @@ import {
|
|
|
24
24
|
createFetchWithConsumedBody,
|
|
25
25
|
} from "./fetch"
|
|
26
26
|
import { stream as streamFn } from "./stream-api"
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
handleErrorResponse,
|
|
29
|
+
resolveHeaders,
|
|
30
|
+
resolveParams,
|
|
31
|
+
warnIfUsingHttpInBrowser,
|
|
32
|
+
} from "./utils"
|
|
28
33
|
import type { BackoffOptions } from "./fetch"
|
|
29
34
|
import type { queueAsPromised } from "fastq"
|
|
30
35
|
import type {
|
|
@@ -742,6 +747,7 @@ export class DurableStream {
|
|
|
742
747
|
live: options?.live,
|
|
743
748
|
json: options?.json,
|
|
744
749
|
onError: options?.onError ?? this.#onError,
|
|
750
|
+
warnOnHttp: options?.warnOnHttp ?? this.#options.warnOnHttp,
|
|
745
751
|
})
|
|
746
752
|
}
|
|
747
753
|
|
|
@@ -864,4 +870,5 @@ function validateOptions(options: Partial<DurableStreamOptions>): void {
|
|
|
864
870
|
if (options.signal && !(options.signal instanceof AbortSignal)) {
|
|
865
871
|
throw new InvalidSignalError()
|
|
866
872
|
}
|
|
873
|
+
warnIfUsingHttpInBrowser(options.url, options.warnOnHttp)
|
|
867
874
|
}
|
package/src/types.ts
CHANGED
|
@@ -158,6 +158,15 @@ export interface StreamOptions {
|
|
|
158
158
|
* fall back to long-polling mode.
|
|
159
159
|
*/
|
|
160
160
|
sseResilience?: SSEResilienceOptions
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Whether to warn when using HTTP (not HTTPS) URLs in browser environments.
|
|
164
|
+
* HTTP limits browsers to 6 concurrent connections (HTTP/1.1), which can
|
|
165
|
+
* cause slow streams and app freezes with multiple active streams.
|
|
166
|
+
*
|
|
167
|
+
* @default true
|
|
168
|
+
*/
|
|
169
|
+
warnOnHttp?: boolean
|
|
161
170
|
}
|
|
162
171
|
|
|
163
172
|
/**
|
|
@@ -310,6 +319,15 @@ export interface StreamHandleOptions {
|
|
|
310
319
|
* @default true
|
|
311
320
|
*/
|
|
312
321
|
batching?: boolean
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Whether to warn when using HTTP (not HTTPS) URLs in browser environments.
|
|
325
|
+
* HTTP limits browsers to 6 concurrent connections (HTTP/1.1), which can
|
|
326
|
+
* cause slow streams and app freezes with multiple active streams.
|
|
327
|
+
*
|
|
328
|
+
* @default true
|
|
329
|
+
*/
|
|
330
|
+
warnOnHttp?: boolean
|
|
313
331
|
}
|
|
314
332
|
|
|
315
333
|
/**
|
package/src/utils.ts
CHANGED
|
@@ -102,3 +102,123 @@ export async function resolveValue<T>(
|
|
|
102
102
|
}
|
|
103
103
|
return value
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
// Module-level Set to track origins we've already warned about (prevents log spam)
|
|
107
|
+
const warnedOrigins = new Set<string>()
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Safely read NODE_ENV without triggering "process is not defined" errors.
|
|
111
|
+
* Works in both browser and Node.js environments.
|
|
112
|
+
*/
|
|
113
|
+
function getNodeEnvSafely(): string | undefined {
|
|
114
|
+
if (typeof process === `undefined`) return undefined
|
|
115
|
+
// Use optional chaining for process.env in case it's undefined (e.g., in some bundler environments)
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
117
|
+
return process.env?.NODE_ENV
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if we're in a browser environment.
|
|
122
|
+
*/
|
|
123
|
+
function isBrowserEnvironment(): boolean {
|
|
124
|
+
return typeof globalThis.window !== `undefined`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get window.location.href safely, returning undefined if not available.
|
|
129
|
+
*/
|
|
130
|
+
function getWindowLocationHref(): string | undefined {
|
|
131
|
+
if (
|
|
132
|
+
typeof globalThis.window !== `undefined` &&
|
|
133
|
+
typeof globalThis.window.location !== `undefined`
|
|
134
|
+
) {
|
|
135
|
+
return globalThis.window.location.href
|
|
136
|
+
}
|
|
137
|
+
return undefined
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Resolve a URL string, handling relative URLs in browser environments.
|
|
142
|
+
* Returns undefined if the URL cannot be parsed.
|
|
143
|
+
*/
|
|
144
|
+
function resolveUrlMaybe(urlString: string): URL | undefined {
|
|
145
|
+
try {
|
|
146
|
+
// First try parsing as an absolute URL
|
|
147
|
+
return new URL(urlString)
|
|
148
|
+
} catch {
|
|
149
|
+
// If that fails and we're in a browser, try resolving as relative URL
|
|
150
|
+
const base = getWindowLocationHref()
|
|
151
|
+
if (base) {
|
|
152
|
+
try {
|
|
153
|
+
return new URL(urlString, base)
|
|
154
|
+
} catch {
|
|
155
|
+
return undefined
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return undefined
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Warn if using HTTP (not HTTPS) URL in a browser environment.
|
|
164
|
+
* HTTP typically limits browsers to ~6 concurrent connections per origin under HTTP/1.1,
|
|
165
|
+
* which can cause slow streams and app freezes with multiple active streams.
|
|
166
|
+
*
|
|
167
|
+
* Features:
|
|
168
|
+
* - Warns only once per origin to prevent log spam
|
|
169
|
+
* - Handles relative URLs by resolving against window.location.href
|
|
170
|
+
* - Safe to call in Node.js environments (no-op)
|
|
171
|
+
* - Skips warning during tests (NODE_ENV=test)
|
|
172
|
+
*/
|
|
173
|
+
export function warnIfUsingHttpInBrowser(
|
|
174
|
+
url: string | URL,
|
|
175
|
+
warnOnHttp?: boolean
|
|
176
|
+
): void {
|
|
177
|
+
// Skip warning if explicitly disabled
|
|
178
|
+
if (warnOnHttp === false) return
|
|
179
|
+
|
|
180
|
+
// Skip warning during tests
|
|
181
|
+
const nodeEnv = getNodeEnvSafely()
|
|
182
|
+
if (nodeEnv === `test`) {
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Only warn in browser environments
|
|
187
|
+
if (
|
|
188
|
+
!isBrowserEnvironment() ||
|
|
189
|
+
typeof console === `undefined` ||
|
|
190
|
+
typeof console.warn !== `function`
|
|
191
|
+
) {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Parse the URL (handles both absolute and relative URLs)
|
|
196
|
+
const urlStr = url instanceof URL ? url.toString() : url
|
|
197
|
+
const parsedUrl = resolveUrlMaybe(urlStr)
|
|
198
|
+
|
|
199
|
+
if (!parsedUrl) {
|
|
200
|
+
// Could not parse URL - silently skip
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check if URL uses HTTP protocol
|
|
205
|
+
if (parsedUrl.protocol === `http:`) {
|
|
206
|
+
// Only warn once per origin
|
|
207
|
+
if (!warnedOrigins.has(parsedUrl.origin)) {
|
|
208
|
+
warnedOrigins.add(parsedUrl.origin)
|
|
209
|
+
console.warn(
|
|
210
|
+
`[DurableStream] Using HTTP (not HTTPS) typically limits browsers to ~6 concurrent connections per origin under HTTP/1.1. ` +
|
|
211
|
+
`This can cause slow streams and app freezes with multiple active streams. ` +
|
|
212
|
+
`Use HTTPS for HTTP/2 support. See https://electric-sql.com/r/electric-http2 for more information.`
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Reset the HTTP warning state. Only exported for testing purposes.
|
|
220
|
+
* @internal
|
|
221
|
+
*/
|
|
222
|
+
export function _resetHttpWarningForTesting(): void {
|
|
223
|
+
warnedOrigins.clear()
|
|
224
|
+
}
|