@delali/sirannon-db 0.1.4 → 0.1.5
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 +415 -39
- package/dist/backup-scheduler/index.d.ts +2 -2
- package/dist/change-tracker-CFTQ9TSn.d.ts +89 -0
- package/dist/chunk-3MCMONVP.mjs +115 -0
- package/dist/chunk-ER7ODTDA.mjs +23 -0
- package/dist/chunk-GS7T5YMI.mjs +51 -0
- package/dist/{chunk-AX66KWBR.mjs → chunk-UTO3ZAFS.mjs} +226 -64
- package/dist/chunk-UVMVN3OT.mjs +111 -0
- package/dist/client/index.d.ts +99 -42
- package/dist/client/index.mjs +726 -26
- package/dist/core/index.d.ts +11 -108
- package/dist/core/index.mjs +134 -168
- package/dist/{sirannon-B1oTfebD.d.ts → database-BVY1GqE7.d.ts} +8 -33
- package/dist/errors-C00ed08Q.d.ts +101 -0
- package/dist/file-migrations/index.d.ts +2 -2
- package/dist/{index-hXiis3N-.d.ts → index-CLdNrcPz.d.ts} +1 -1
- package/dist/replication/coordinator/etcd.d.ts +44 -0
- package/dist/replication/coordinator/etcd.mjs +650 -0
- package/dist/replication/index.d.ts +491 -0
- package/dist/replication/index.mjs +3784 -0
- package/dist/server/index.d.ts +14 -3
- package/dist/server/index.mjs +262 -44
- package/dist/sirannon-Cd-lK6T0.d.ts +31 -0
- package/dist/transport/grpc.d.ts +316 -0
- package/dist/transport/grpc.mjs +3341 -0
- package/dist/transport/memory.d.ts +221 -0
- package/dist/transport/memory.mjs +337 -0
- package/dist/types-B2byqt0B.d.ts +273 -0
- package/dist/types-BEu1I_9_.d.ts +139 -0
- package/dist/types-BeozgNPr.d.ts +26 -0
- package/dist/{types-DtDutWRU.d.ts → types-D-74JiXb.d.ts} +78 -2
- package/package.json +54 -10
- package/dist/types-DRkJlqex.d.ts +0 -38
package/dist/client/index.mjs
CHANGED
|
@@ -32,8 +32,8 @@ var RemoteDatabase = class {
|
|
|
32
32
|
* )
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
|
-
async query(sql, params) {
|
|
36
|
-
const response = await this.transport.query(sql, params);
|
|
35
|
+
async query(sql, params, options) {
|
|
36
|
+
const response = await this.transport.query(sql, params, options?.readConcern);
|
|
37
37
|
return response.rows;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
@@ -104,8 +104,8 @@ var HttpTransport = class {
|
|
|
104
104
|
...headers
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
|
-
async query(sql, params) {
|
|
108
|
-
return this.post("/query", { sql, params });
|
|
107
|
+
async query(sql, params, readConcern) {
|
|
108
|
+
return this.post("/query", { sql, params, readConcern });
|
|
109
109
|
}
|
|
110
110
|
async execute(sql, params) {
|
|
111
111
|
return this.post("/execute", { sql, params });
|
|
@@ -165,6 +165,7 @@ var WebSocketTransport = class {
|
|
|
165
165
|
autoReconnect;
|
|
166
166
|
reconnectInterval;
|
|
167
167
|
requestTimeout;
|
|
168
|
+
protocols;
|
|
168
169
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
169
170
|
activeSubscriptions = /* @__PURE__ */ new Map();
|
|
170
171
|
idCounter = 0;
|
|
@@ -176,6 +177,7 @@ var WebSocketTransport = class {
|
|
|
176
177
|
this.autoReconnect = options?.autoReconnect ?? true;
|
|
177
178
|
this.reconnectInterval = options?.reconnectInterval ?? 1e3;
|
|
178
179
|
this.requestTimeout = options?.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
180
|
+
this.protocols = options?.protocols;
|
|
179
181
|
}
|
|
180
182
|
async query(sql, params) {
|
|
181
183
|
await this.ensureConnected();
|
|
@@ -249,7 +251,7 @@ var WebSocketTransport = class {
|
|
|
249
251
|
connect() {
|
|
250
252
|
return new Promise((resolve, reject) => {
|
|
251
253
|
let settled = false;
|
|
252
|
-
const ws = new WebSocket(this.url);
|
|
254
|
+
const ws = this.protocols === void 0 ? new WebSocket(this.url) : new WebSocket(this.url, this.protocols);
|
|
253
255
|
const onOpen = () => {
|
|
254
256
|
settled = true;
|
|
255
257
|
this.ws = ws;
|
|
@@ -413,30 +415,192 @@ var WebSocketTransport = class {
|
|
|
413
415
|
};
|
|
414
416
|
|
|
415
417
|
// src/client/client.ts
|
|
418
|
+
var CLUSTER_DISCOVERY_FETCH_TIMEOUT_MS = 2e3;
|
|
419
|
+
function clusterRoutingFingerprint(state) {
|
|
420
|
+
const readEndpoints = state.readEndpoints.map((endpoint) => ({
|
|
421
|
+
url: endpoint.url,
|
|
422
|
+
readConcerns: [...endpoint.readConcerns].sort()
|
|
423
|
+
})).sort((left, right) => left.url.localeCompare(right.url));
|
|
424
|
+
return JSON.stringify({
|
|
425
|
+
currentPrimary: state.currentPrimary,
|
|
426
|
+
primaryTerm: state.primaryTerm,
|
|
427
|
+
readEndpoints
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
function clusterRoutingChanged(previous, next) {
|
|
431
|
+
return previous === void 0 || clusterRoutingFingerprint(previous) !== clusterRoutingFingerprint(next);
|
|
432
|
+
}
|
|
433
|
+
function isTopologyConfig(urlOrOpts) {
|
|
434
|
+
if (typeof urlOrOpts !== "object") {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
return "primary" in urlOrOpts || "replicas" in urlOrOpts && Array.isArray(urlOrOpts.replicas) && urlOrOpts.replicas.length > 0 || "endpoints" in urlOrOpts && Array.isArray(urlOrOpts.endpoints) && urlOrOpts.endpoints.length > 0 || urlOrOpts.discovery === "coordinator";
|
|
438
|
+
}
|
|
439
|
+
function toBaseUrl(url) {
|
|
440
|
+
return normaliseEndpointUrl(url);
|
|
441
|
+
}
|
|
442
|
+
function toServerBaseUrl(url, databaseId) {
|
|
443
|
+
const base = toBaseUrl(url);
|
|
444
|
+
if (!databaseId) return base.replace(/\/db\/[^/]+$/i, "");
|
|
445
|
+
return base.replace(new RegExp(`/db/${escapeRegExp(encodeURIComponent(databaseId))}$`, "i"), "");
|
|
446
|
+
}
|
|
447
|
+
function toWsUrl(baseUrl) {
|
|
448
|
+
return baseUrl.replace(/^http:\/\//i, "ws://").replace(/^https:\/\//i, "wss://");
|
|
449
|
+
}
|
|
450
|
+
function escapeRegExp(value) {
|
|
451
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
452
|
+
}
|
|
453
|
+
function normaliseEndpointUrl(rawUrl) {
|
|
454
|
+
let parsed;
|
|
455
|
+
try {
|
|
456
|
+
parsed = new URL(rawUrl);
|
|
457
|
+
} catch {
|
|
458
|
+
throw new TypeError(`Endpoint URL '${rawUrl}' is invalid`);
|
|
459
|
+
}
|
|
460
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
461
|
+
throw new TypeError(`Endpoint URL '${rawUrl}' must use http or https`);
|
|
462
|
+
}
|
|
463
|
+
if (parsed.username || parsed.password) {
|
|
464
|
+
throw new TypeError(`Endpoint URL '${rawUrl}' must not contain credentials`);
|
|
465
|
+
}
|
|
466
|
+
if (parsed.hash) {
|
|
467
|
+
throw new TypeError(`Endpoint URL '${rawUrl}' must not contain a fragment`);
|
|
468
|
+
}
|
|
469
|
+
if (parsed.search) {
|
|
470
|
+
throw new TypeError(`Endpoint URL '${rawUrl}' must not contain a query string`);
|
|
471
|
+
}
|
|
472
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
473
|
+
return parsed.toString().replace(/\/$/, "");
|
|
474
|
+
}
|
|
475
|
+
function isReadConcernLevel(value) {
|
|
476
|
+
return value === "local" || value === "majority" || value === "linearizable";
|
|
477
|
+
}
|
|
478
|
+
function parseDiscoveredReadConcerns(value) {
|
|
479
|
+
if (!Array.isArray(value)) {
|
|
480
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata readConcerns must be an array");
|
|
481
|
+
}
|
|
482
|
+
const concerns = [];
|
|
483
|
+
for (const concern of value) {
|
|
484
|
+
if (!isReadConcernLevel(concern)) {
|
|
485
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata contains an invalid read concern");
|
|
486
|
+
}
|
|
487
|
+
if (!concerns.includes(concern)) {
|
|
488
|
+
concerns.push(concern);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return concerns;
|
|
492
|
+
}
|
|
493
|
+
function toDiscoveredServerBaseUrl(endpoint, databaseId) {
|
|
494
|
+
try {
|
|
495
|
+
return toServerBaseUrl(endpoint, databaseId);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
498
|
+
throw new RemoteError("INVALID_RESPONSE", `Cluster metadata contains an unsafe endpoint: ${message}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function parseClusterRouting(data, databaseId) {
|
|
502
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
503
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata must be an object");
|
|
504
|
+
}
|
|
505
|
+
const record = data;
|
|
506
|
+
if (record.databaseId !== databaseId) {
|
|
507
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata database id does not match the request");
|
|
508
|
+
}
|
|
509
|
+
if (record.primaryTerm !== void 0 && typeof record.primaryTerm !== "string") {
|
|
510
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata primaryTerm must be a string");
|
|
511
|
+
}
|
|
512
|
+
let currentPrimary = null;
|
|
513
|
+
if (record.currentPrimary !== void 0 && record.currentPrimary !== null) {
|
|
514
|
+
if (typeof record.currentPrimary !== "object" || Array.isArray(record.currentPrimary)) {
|
|
515
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata currentPrimary must be an object or null");
|
|
516
|
+
}
|
|
517
|
+
const primary = record.currentPrimary;
|
|
518
|
+
if (typeof primary.endpoint !== "string") {
|
|
519
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata currentPrimary.endpoint must be a string");
|
|
520
|
+
}
|
|
521
|
+
currentPrimary = toDiscoveredServerBaseUrl(primary.endpoint, databaseId);
|
|
522
|
+
}
|
|
523
|
+
const readEndpointsRaw = record.readEndpoints;
|
|
524
|
+
if (readEndpointsRaw !== void 0 && !Array.isArray(readEndpointsRaw)) {
|
|
525
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata readEndpoints must be an array");
|
|
526
|
+
}
|
|
527
|
+
const readEndpoints = (readEndpointsRaw ?? []).map((endpointInfo) => {
|
|
528
|
+
if (typeof endpointInfo !== "object" || endpointInfo === null || Array.isArray(endpointInfo)) {
|
|
529
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata read endpoint must be an object");
|
|
530
|
+
}
|
|
531
|
+
const endpoint = endpointInfo;
|
|
532
|
+
if (typeof endpoint.endpoint !== "string") {
|
|
533
|
+
throw new RemoteError("INVALID_RESPONSE", "Cluster metadata read endpoint URL must be a string");
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
url: toDiscoveredServerBaseUrl(endpoint.endpoint, databaseId),
|
|
537
|
+
readConcerns: parseDiscoveredReadConcerns(endpoint.readConcerns)
|
|
538
|
+
};
|
|
539
|
+
});
|
|
540
|
+
return {
|
|
541
|
+
currentPrimary,
|
|
542
|
+
primaryTerm: record.primaryTerm ?? null,
|
|
543
|
+
readEndpoints
|
|
544
|
+
};
|
|
545
|
+
}
|
|
416
546
|
var SirannonClient = class {
|
|
417
547
|
baseUrl;
|
|
418
548
|
wsBaseUrl;
|
|
419
549
|
transport;
|
|
420
550
|
headers;
|
|
551
|
+
webSocketProtocols;
|
|
421
552
|
autoReconnect;
|
|
422
553
|
reconnectInterval;
|
|
423
554
|
databases = /* @__PURE__ */ new Map();
|
|
424
555
|
closed = false;
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
556
|
+
topologyEnabled;
|
|
557
|
+
primaryUrl;
|
|
558
|
+
replicaUrls;
|
|
559
|
+
readPreference;
|
|
560
|
+
discovery;
|
|
561
|
+
readConcern;
|
|
562
|
+
starterEndpoints;
|
|
563
|
+
clusterRouting = /* @__PURE__ */ new Map();
|
|
564
|
+
topologyTransports = /* @__PURE__ */ new Map();
|
|
565
|
+
latencies = [];
|
|
566
|
+
latencyMeasuredAt = 0;
|
|
567
|
+
latencyMeasuring = null;
|
|
568
|
+
LATENCY_TTL_MS = 6e4;
|
|
569
|
+
removedReplicas = /* @__PURE__ */ new Set();
|
|
570
|
+
constructor(urlOrOpts, options) {
|
|
571
|
+
if (isTopologyConfig(urlOrOpts)) {
|
|
572
|
+
const topoOpts = urlOrOpts;
|
|
573
|
+
this.topologyEnabled = true;
|
|
574
|
+
this.primaryUrl = topoOpts.primary ? toBaseUrl(topoOpts.primary) : void 0;
|
|
575
|
+
this.replicaUrls = (topoOpts.replicas ?? []).map(toBaseUrl);
|
|
576
|
+
this.readPreference = topoOpts.readPreference ?? "primary";
|
|
577
|
+
this.discovery = topoOpts.discovery ?? "static";
|
|
578
|
+
this.readConcern = topoOpts.readConcern;
|
|
579
|
+
this.starterEndpoints = (topoOpts.endpoints ?? []).map(toBaseUrl);
|
|
580
|
+
this.baseUrl = this.primaryUrl ?? this.replicaUrls[0] ?? this.starterEndpoints[0] ?? "";
|
|
581
|
+
this.wsBaseUrl = toWsUrl(this.baseUrl);
|
|
582
|
+
this.transport = topoOpts.transport ?? "websocket";
|
|
583
|
+
this.headers = topoOpts.headers;
|
|
584
|
+
this.webSocketProtocols = topoOpts.webSocketProtocols;
|
|
585
|
+
this.autoReconnect = topoOpts.autoReconnect ?? true;
|
|
586
|
+
this.reconnectInterval = topoOpts.reconnectInterval ?? 1e3;
|
|
587
|
+
} else {
|
|
588
|
+
this.topologyEnabled = false;
|
|
589
|
+
this.primaryUrl = void 0;
|
|
590
|
+
this.replicaUrls = [];
|
|
591
|
+
this.readPreference = "primary";
|
|
592
|
+
this.discovery = "static";
|
|
593
|
+
this.readConcern = void 0;
|
|
594
|
+
this.starterEndpoints = [];
|
|
595
|
+
this.baseUrl = toBaseUrl(urlOrOpts);
|
|
596
|
+
this.wsBaseUrl = toWsUrl(this.baseUrl);
|
|
597
|
+
this.transport = options?.transport ?? "websocket";
|
|
598
|
+
this.headers = options?.headers;
|
|
599
|
+
this.webSocketProtocols = options?.webSocketProtocols;
|
|
600
|
+
this.autoReconnect = options?.autoReconnect ?? true;
|
|
601
|
+
this.reconnectInterval = options?.reconnectInterval ?? 1e3;
|
|
602
|
+
}
|
|
432
603
|
}
|
|
433
|
-
/**
|
|
434
|
-
* Get a {@link RemoteDatabase} proxy for the given database ID.
|
|
435
|
-
* Returns a cached instance if one already exists for this ID.
|
|
436
|
-
*
|
|
437
|
-
* The underlying transport connection is established lazily on
|
|
438
|
-
* the first operation (query, execute, or subscribe).
|
|
439
|
-
*/
|
|
440
604
|
database(id) {
|
|
441
605
|
if (this.closed) {
|
|
442
606
|
throw new Error("Client is closed");
|
|
@@ -452,10 +616,6 @@ var SirannonClient = class {
|
|
|
452
616
|
this.databases.set(id, db);
|
|
453
617
|
return db;
|
|
454
618
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Close all database connections and release resources.
|
|
457
|
-
* After calling `close()`, new calls to `database()` will throw.
|
|
458
|
-
*/
|
|
459
619
|
close() {
|
|
460
620
|
this.closed = true;
|
|
461
621
|
const openDatabases = [...this.databases.values()];
|
|
@@ -465,15 +625,555 @@ var SirannonClient = class {
|
|
|
465
625
|
}
|
|
466
626
|
}
|
|
467
627
|
createTransport(databaseId) {
|
|
628
|
+
if (!this.topologyEnabled) {
|
|
629
|
+
return this.createTransportForUrl(this.baseUrl, this.wsBaseUrl, databaseId);
|
|
630
|
+
}
|
|
631
|
+
const transport = new TopologyAwareTransport(databaseId, this);
|
|
632
|
+
this.topologyTransports.set(databaseId, transport);
|
|
633
|
+
return transport;
|
|
634
|
+
}
|
|
635
|
+
createTransportForUrl(baseUrl, wsBaseUrl, databaseId) {
|
|
468
636
|
const encodedId = encodeURIComponent(databaseId);
|
|
469
637
|
if (this.transport === "http") {
|
|
470
|
-
return new HttpTransport(`${
|
|
638
|
+
return new HttpTransport(`${baseUrl}/db/${encodedId}`, this.headers);
|
|
471
639
|
}
|
|
472
|
-
return new WebSocketTransport(`${
|
|
640
|
+
return new WebSocketTransport(`${wsBaseUrl}/db/${encodedId}`, {
|
|
473
641
|
autoReconnect: this.autoReconnect,
|
|
474
|
-
reconnectInterval: this.reconnectInterval
|
|
642
|
+
reconnectInterval: this.reconnectInterval,
|
|
643
|
+
protocols: this.webSocketProtocols
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
_createTransportForEndpoint(url, databaseId) {
|
|
647
|
+
const base = toBaseUrl(url);
|
|
648
|
+
const ws = toWsUrl(base);
|
|
649
|
+
return this.createTransportForUrl(base, ws, databaseId);
|
|
650
|
+
}
|
|
651
|
+
async _getReadEndpoint(databaseId, readConcern) {
|
|
652
|
+
if (this.discovery === "coordinator" && databaseId) {
|
|
653
|
+
const routing = await this.ensureClusterRouting(databaseId);
|
|
654
|
+
const concern = readConcern ?? this.readConcern ?? "majority";
|
|
655
|
+
if (concern === "linearizable") {
|
|
656
|
+
if (routing.currentPrimary) return routing.currentPrimary;
|
|
657
|
+
throw new RemoteError("NO_SAFE_PRIMARY", "No current primary is available for linearizable reads");
|
|
658
|
+
}
|
|
659
|
+
const readable = routing.readEndpoints.filter((endpoint) => endpoint.readConcerns.includes(concern));
|
|
660
|
+
const preferredReadable = this.readPreference === "replica" && routing.currentPrimary ? readable.filter((endpoint) => endpoint.url !== routing.currentPrimary) : readable;
|
|
661
|
+
if (this.readPreference !== "primary" && preferredReadable.length > 0) {
|
|
662
|
+
if (this.readPreference === "nearest") {
|
|
663
|
+
return preferredReadable[0].url;
|
|
664
|
+
}
|
|
665
|
+
const idx = Math.floor(Math.random() * preferredReadable.length);
|
|
666
|
+
return preferredReadable[idx].url;
|
|
667
|
+
}
|
|
668
|
+
if (routing.currentPrimary) return routing.currentPrimary;
|
|
669
|
+
const localReadable = routing.readEndpoints.find((endpoint) => endpoint.readConcerns.includes("local"));
|
|
670
|
+
if (localReadable) return localReadable.url;
|
|
671
|
+
throw new RemoteError("ROUTING_ERROR", "No usable read endpoint is available");
|
|
672
|
+
}
|
|
673
|
+
if (this.readPreference === "primary") {
|
|
674
|
+
return this.primaryUrl ?? this.baseUrl;
|
|
675
|
+
}
|
|
676
|
+
const availableReplicas = this.replicaUrls.filter((r) => !this.removedReplicas.has(r));
|
|
677
|
+
if (this.readPreference === "replica") {
|
|
678
|
+
if (availableReplicas.length === 0) {
|
|
679
|
+
return this.primaryUrl ?? this.baseUrl;
|
|
680
|
+
}
|
|
681
|
+
const idx = Math.floor(Math.random() * availableReplicas.length);
|
|
682
|
+
return availableReplicas[idx];
|
|
683
|
+
}
|
|
684
|
+
if (this.readPreference === "nearest") {
|
|
685
|
+
await this.ensureLatencyMeasured();
|
|
686
|
+
const reachable = this.latencies.filter((l) => l.reachable && !this.removedReplicas.has(l.url));
|
|
687
|
+
if (reachable.length === 0) {
|
|
688
|
+
return this.primaryUrl ?? this.baseUrl;
|
|
689
|
+
}
|
|
690
|
+
reachable.sort((a, b) => a.latencyMs - b.latencyMs);
|
|
691
|
+
return reachable[0].url;
|
|
692
|
+
}
|
|
693
|
+
return this.primaryUrl ?? this.baseUrl;
|
|
694
|
+
}
|
|
695
|
+
async _getWriteEndpoint(databaseId) {
|
|
696
|
+
if (this.discovery === "coordinator" && databaseId) {
|
|
697
|
+
const routing = await this.ensureClusterRouting(databaseId);
|
|
698
|
+
if (!routing.currentPrimary) {
|
|
699
|
+
throw new RemoteError("NO_SAFE_PRIMARY", "No current primary is available");
|
|
700
|
+
}
|
|
701
|
+
return routing.currentPrimary;
|
|
702
|
+
}
|
|
703
|
+
return this.primaryUrl ?? this.baseUrl;
|
|
704
|
+
}
|
|
705
|
+
_getReadConcern() {
|
|
706
|
+
return this.readConcern;
|
|
707
|
+
}
|
|
708
|
+
_usesCoordinatorDiscovery() {
|
|
709
|
+
return this.discovery === "coordinator";
|
|
710
|
+
}
|
|
711
|
+
_removeReplica(url) {
|
|
712
|
+
this.removedReplicas.add(url);
|
|
713
|
+
}
|
|
714
|
+
async ensureLatencyMeasured() {
|
|
715
|
+
const now = Date.now();
|
|
716
|
+
if (this.latencies.length > 0 && now - this.latencyMeasuredAt < this.LATENCY_TTL_MS) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (this.latencyMeasuring) {
|
|
720
|
+
await this.latencyMeasuring;
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
this.latencyMeasuring = this.measureLatencies();
|
|
724
|
+
try {
|
|
725
|
+
await this.latencyMeasuring;
|
|
726
|
+
} finally {
|
|
727
|
+
this.latencyMeasuring = null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async measureLatencies() {
|
|
731
|
+
const allEndpoints = [];
|
|
732
|
+
if (this.primaryUrl) {
|
|
733
|
+
allEndpoints.push(this.primaryUrl);
|
|
734
|
+
}
|
|
735
|
+
for (const r of this.replicaUrls) {
|
|
736
|
+
allEndpoints.push(r);
|
|
737
|
+
}
|
|
738
|
+
const results = await Promise.all(
|
|
739
|
+
allEndpoints.map(async (url) => {
|
|
740
|
+
const start = performance.now();
|
|
741
|
+
const controller = new AbortController();
|
|
742
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
743
|
+
const unrefable = timeout;
|
|
744
|
+
if (typeof unrefable.unref === "function") {
|
|
745
|
+
unrefable.unref();
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
const init = { signal: controller.signal };
|
|
749
|
+
const response = await fetch(`${url}/health`, init);
|
|
750
|
+
if (!response.ok) {
|
|
751
|
+
return { url, latencyMs: Number.MAX_SAFE_INTEGER, reachable: false };
|
|
752
|
+
}
|
|
753
|
+
return { url, latencyMs: performance.now() - start, reachable: true };
|
|
754
|
+
} catch {
|
|
755
|
+
return { url, latencyMs: Number.MAX_SAFE_INTEGER, reachable: false };
|
|
756
|
+
} finally {
|
|
757
|
+
clearTimeout(timeout);
|
|
758
|
+
}
|
|
759
|
+
})
|
|
760
|
+
);
|
|
761
|
+
this.latencies = results;
|
|
762
|
+
this.latencyMeasuredAt = Date.now();
|
|
763
|
+
}
|
|
764
|
+
async _refreshClusterRouting(databaseId) {
|
|
765
|
+
const candidates = this.clusterDiscoveryCandidates(databaseId);
|
|
766
|
+
const encodedId = encodeURIComponent(databaseId);
|
|
767
|
+
for (const endpoint of candidates) {
|
|
768
|
+
const base = toServerBaseUrl(endpoint, databaseId);
|
|
769
|
+
const controller = new AbortController();
|
|
770
|
+
const timeout = setTimeout(() => controller.abort(), CLUSTER_DISCOVERY_FETCH_TIMEOUT_MS);
|
|
771
|
+
const unrefable = timeout;
|
|
772
|
+
unrefable.unref?.();
|
|
773
|
+
let next = null;
|
|
774
|
+
try {
|
|
775
|
+
const response = await fetch(`${base}/db/${encodedId}/cluster`, {
|
|
776
|
+
headers: this.headers,
|
|
777
|
+
signal: controller.signal
|
|
778
|
+
});
|
|
779
|
+
if (!response.ok) {
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
const data = await response.json();
|
|
783
|
+
next = parseClusterRouting(data, databaseId);
|
|
784
|
+
} catch (err) {
|
|
785
|
+
if (err instanceof RemoteError && err.code === "INVALID_RESPONSE") {
|
|
786
|
+
throw err;
|
|
787
|
+
}
|
|
788
|
+
} finally {
|
|
789
|
+
clearTimeout(timeout);
|
|
790
|
+
}
|
|
791
|
+
if (!next) {
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const previous = this.clusterRouting.get(databaseId);
|
|
795
|
+
this.clusterRouting.set(databaseId, next);
|
|
796
|
+
if (clusterRoutingChanged(previous, next)) {
|
|
797
|
+
try {
|
|
798
|
+
await this.notifyClusterRoutingChanged(databaseId);
|
|
799
|
+
} catch (err) {
|
|
800
|
+
if (previous) {
|
|
801
|
+
this.clusterRouting.set(databaseId, previous);
|
|
802
|
+
} else {
|
|
803
|
+
this.clusterRouting.delete(databaseId);
|
|
804
|
+
}
|
|
805
|
+
throw err;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
throw new RemoteError("ROUTING_ERROR", `Could not discover cluster routing for database '${databaseId}'`);
|
|
811
|
+
}
|
|
812
|
+
async ensureClusterRouting(databaseId) {
|
|
813
|
+
const existing = this.clusterRouting.get(databaseId);
|
|
814
|
+
if (existing) return existing;
|
|
815
|
+
await this._refreshClusterRouting(databaseId);
|
|
816
|
+
const refreshed = this.clusterRouting.get(databaseId);
|
|
817
|
+
if (!refreshed) {
|
|
818
|
+
throw new RemoteError("ROUTING_ERROR", `Could not discover cluster routing for database '${databaseId}'`);
|
|
819
|
+
}
|
|
820
|
+
return refreshed;
|
|
821
|
+
}
|
|
822
|
+
clusterDiscoveryCandidates(databaseId) {
|
|
823
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
824
|
+
for (const endpoint of this.starterEndpoints) candidates.add(endpoint);
|
|
825
|
+
if (this.primaryUrl) candidates.add(this.primaryUrl);
|
|
826
|
+
for (const endpoint of this.replicaUrls) candidates.add(endpoint);
|
|
827
|
+
const existing = this.clusterRouting.get(databaseId);
|
|
828
|
+
if (existing?.currentPrimary) candidates.add(existing.currentPrimary);
|
|
829
|
+
for (const endpoint of existing?.readEndpoints ?? []) candidates.add(endpoint.url);
|
|
830
|
+
if (this.baseUrl) candidates.add(this.baseUrl);
|
|
831
|
+
return [...candidates];
|
|
832
|
+
}
|
|
833
|
+
_unregisterTopologyTransport(databaseId, transport) {
|
|
834
|
+
if (this.topologyTransports.get(databaseId) === transport) {
|
|
835
|
+
this.topologyTransports.delete(databaseId);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
async notifyClusterRoutingChanged(databaseId) {
|
|
839
|
+
const transport = this.topologyTransports.get(databaseId);
|
|
840
|
+
if (!transport) return;
|
|
841
|
+
await transport._handleClusterRoutingChanged();
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
var TopologyAwareTransport = class {
|
|
845
|
+
databaseId;
|
|
846
|
+
client;
|
|
847
|
+
closed = false;
|
|
848
|
+
readTransport = null;
|
|
849
|
+
writeTransport = null;
|
|
850
|
+
subscriptionTransport = null;
|
|
851
|
+
readTransportRequest = null;
|
|
852
|
+
writeTransportRequest = null;
|
|
853
|
+
subscriptionTransportRequest = null;
|
|
854
|
+
subscriptionOperation = Promise.resolve();
|
|
855
|
+
activeSubscriptions = /* @__PURE__ */ new Map();
|
|
856
|
+
nextSubscriptionId = 0;
|
|
857
|
+
currentReadUrl = "";
|
|
858
|
+
currentWriteUrl = "";
|
|
859
|
+
currentSubscriptionUrl = "";
|
|
860
|
+
constructor(databaseId, client) {
|
|
861
|
+
this.databaseId = databaseId;
|
|
862
|
+
this.client = client;
|
|
863
|
+
}
|
|
864
|
+
async query(sql, params) {
|
|
865
|
+
const readConcern = this.client._getReadConcern();
|
|
866
|
+
const transport = await this.getReadTransport(readConcern);
|
|
867
|
+
const endpointUsed = this.currentReadUrl;
|
|
868
|
+
try {
|
|
869
|
+
return await transport.query(sql, params, readConcern ? { level: readConcern } : void 0);
|
|
870
|
+
} catch (err) {
|
|
871
|
+
if (this.client._usesCoordinatorDiscovery() && shouldRefreshRouting(err)) {
|
|
872
|
+
await this.client._refreshClusterRouting(this.databaseId);
|
|
873
|
+
this.readTransport = null;
|
|
874
|
+
this.currentReadUrl = "";
|
|
875
|
+
const refreshed = await this.getReadTransport(readConcern);
|
|
876
|
+
return refreshed.query(sql, params, readConcern ? { level: readConcern } : void 0);
|
|
877
|
+
}
|
|
878
|
+
const isTransportError = err instanceof Error && (err.name !== "RemoteError" || err.code === "CONNECTION_ERROR");
|
|
879
|
+
const writeEndpoint = await this.client._getWriteEndpoint(this.databaseId);
|
|
880
|
+
if (isTransportError && endpointUsed && endpointUsed !== writeEndpoint) {
|
|
881
|
+
this.client._removeReplica(endpointUsed);
|
|
882
|
+
if (this.currentReadUrl === endpointUsed) {
|
|
883
|
+
this.readTransport = null;
|
|
884
|
+
this.currentReadUrl = "";
|
|
885
|
+
}
|
|
886
|
+
const fallback = await this.getReadTransport();
|
|
887
|
+
return fallback.query(sql, params);
|
|
888
|
+
}
|
|
889
|
+
throw err;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
async execute(sql, params) {
|
|
893
|
+
const transport = await this.getWriteTransport();
|
|
894
|
+
try {
|
|
895
|
+
return await transport.execute(sql, params);
|
|
896
|
+
} catch (err) {
|
|
897
|
+
if (this.client._usesCoordinatorDiscovery() && shouldRefreshRouting(err)) {
|
|
898
|
+
await this.client._refreshClusterRouting(this.databaseId);
|
|
899
|
+
this.writeTransport = null;
|
|
900
|
+
this.currentWriteUrl = "";
|
|
901
|
+
}
|
|
902
|
+
throw err;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
async transaction(statements) {
|
|
906
|
+
const transport = await this.getWriteTransport();
|
|
907
|
+
try {
|
|
908
|
+
return await transport.transaction(statements);
|
|
909
|
+
} catch (err) {
|
|
910
|
+
if (this.client._usesCoordinatorDiscovery() && shouldRefreshRouting(err)) {
|
|
911
|
+
await this.client._refreshClusterRouting(this.databaseId);
|
|
912
|
+
this.writeTransport = null;
|
|
913
|
+
this.currentWriteUrl = "";
|
|
914
|
+
}
|
|
915
|
+
throw err;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async subscribe(table, filter, callback) {
|
|
919
|
+
try {
|
|
920
|
+
return await this.subscribeOnCurrentEndpoint(table, filter, callback);
|
|
921
|
+
} catch (err) {
|
|
922
|
+
if (this.client._usesCoordinatorDiscovery() && shouldRefreshRouting(err)) {
|
|
923
|
+
const hadActiveSubscriptions = this.activeSubscriptions.size > 0;
|
|
924
|
+
if (!hadActiveSubscriptions) {
|
|
925
|
+
this.closeSubscriptionTransport();
|
|
926
|
+
}
|
|
927
|
+
await this.client._refreshClusterRouting(this.databaseId);
|
|
928
|
+
if (!hadActiveSubscriptions) {
|
|
929
|
+
this.closeSubscriptionTransport();
|
|
930
|
+
}
|
|
931
|
+
return this.subscribeOnCurrentEndpoint(table, filter, callback);
|
|
932
|
+
}
|
|
933
|
+
throw err;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
async _handleClusterRoutingChanged() {
|
|
937
|
+
if (!this.client._usesCoordinatorDiscovery()) return;
|
|
938
|
+
if (this.activeSubscriptions.size === 0) return;
|
|
939
|
+
await this.withSubscriptionOperation(() => this.migrateSubscriptionsToCurrentEndpoint());
|
|
940
|
+
}
|
|
941
|
+
close() {
|
|
942
|
+
this.closed = true;
|
|
943
|
+
const sameTransport = this.readTransport !== null && this.readTransport === this.writeTransport;
|
|
944
|
+
const subscriptionIsRead = this.subscriptionTransport !== null && this.subscriptionTransport === this.readTransport;
|
|
945
|
+
const subscriptionIsWrite = this.subscriptionTransport !== null && this.subscriptionTransport === this.writeTransport;
|
|
946
|
+
for (const subscription of this.activeSubscriptions.values()) {
|
|
947
|
+
subscription.active = false;
|
|
948
|
+
subscription.remote = null;
|
|
949
|
+
}
|
|
950
|
+
this.activeSubscriptions.clear();
|
|
951
|
+
if (this.readTransport) {
|
|
952
|
+
this.readTransport.close();
|
|
953
|
+
this.readTransport = null;
|
|
954
|
+
}
|
|
955
|
+
if (this.writeTransport && !sameTransport) {
|
|
956
|
+
this.writeTransport.close();
|
|
957
|
+
}
|
|
958
|
+
this.writeTransport = null;
|
|
959
|
+
if (this.subscriptionTransport && !subscriptionIsRead && !subscriptionIsWrite) {
|
|
960
|
+
this.subscriptionTransport.close();
|
|
961
|
+
}
|
|
962
|
+
this.subscriptionTransport = null;
|
|
963
|
+
this.currentSubscriptionUrl = "";
|
|
964
|
+
this.client._unregisterTopologyTransport(this.databaseId, this);
|
|
965
|
+
}
|
|
966
|
+
async subscribeOnCurrentEndpoint(table, filter, callback) {
|
|
967
|
+
return this.withSubscriptionOperation(async () => {
|
|
968
|
+
const transport = await this.getSubscriptionTransport(this.client._getReadConcern());
|
|
969
|
+
const remote = await transport.subscribe(table, filter, callback);
|
|
970
|
+
const subscription = {
|
|
971
|
+
id: ++this.nextSubscriptionId,
|
|
972
|
+
table,
|
|
973
|
+
filter,
|
|
974
|
+
callback,
|
|
975
|
+
remote,
|
|
976
|
+
active: true
|
|
977
|
+
};
|
|
978
|
+
this.activeSubscriptions.set(subscription.id, subscription);
|
|
979
|
+
return this.createSubscriptionHandle(subscription);
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
createSubscriptionHandle(subscription) {
|
|
983
|
+
return {
|
|
984
|
+
unsubscribe: () => {
|
|
985
|
+
if (!subscription.active) return;
|
|
986
|
+
subscription.active = false;
|
|
987
|
+
this.activeSubscriptions.delete(subscription.id);
|
|
988
|
+
const remote = subscription.remote;
|
|
989
|
+
subscription.remote = null;
|
|
990
|
+
remote?.unsubscribe();
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
async migrateSubscriptionsToCurrentEndpoint() {
|
|
995
|
+
this.assertOpen();
|
|
996
|
+
const subscriptions = [...this.activeSubscriptions.values()].filter((subscription) => subscription.active);
|
|
997
|
+
if (subscriptions.length === 0) return;
|
|
998
|
+
const readConcern = this.client._getReadConcern();
|
|
999
|
+
const endpoint = await this.client._getReadEndpoint(this.databaseId, readConcern);
|
|
1000
|
+
this.assertOpen();
|
|
1001
|
+
if (this.subscriptionTransport && this.currentSubscriptionUrl === endpoint) {
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
const nextTransport = this.client._createTransportForEndpoint(endpoint, this.databaseId);
|
|
1005
|
+
const nextSubscriptions = /* @__PURE__ */ new Map();
|
|
1006
|
+
try {
|
|
1007
|
+
for (const subscription of subscriptions) {
|
|
1008
|
+
if (!subscription.active) continue;
|
|
1009
|
+
const remote = await nextTransport.subscribe(subscription.table, subscription.filter, subscription.callback);
|
|
1010
|
+
if (!subscription.active) {
|
|
1011
|
+
remote.unsubscribe();
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
nextSubscriptions.set(subscription.id, remote);
|
|
1015
|
+
}
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
for (const remote of nextSubscriptions.values()) {
|
|
1018
|
+
remote.unsubscribe();
|
|
1019
|
+
}
|
|
1020
|
+
nextTransport.close();
|
|
1021
|
+
throw toSubscriptionRoutingError(err, this.databaseId);
|
|
1022
|
+
}
|
|
1023
|
+
const oldTransport = this.subscriptionTransport;
|
|
1024
|
+
this.subscriptionTransport = nextTransport;
|
|
1025
|
+
this.currentSubscriptionUrl = endpoint;
|
|
1026
|
+
for (const subscription of subscriptions) {
|
|
1027
|
+
const nextRemote = nextSubscriptions.get(subscription.id);
|
|
1028
|
+
if (!nextRemote) continue;
|
|
1029
|
+
const previousRemote = subscription.remote;
|
|
1030
|
+
if (!subscription.active) {
|
|
1031
|
+
nextRemote.unsubscribe();
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
subscription.remote = nextRemote;
|
|
1035
|
+
previousRemote?.unsubscribe();
|
|
1036
|
+
}
|
|
1037
|
+
if (oldTransport) {
|
|
1038
|
+
oldTransport.close();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
async getReadTransport(readConcern) {
|
|
1042
|
+
this.assertOpen();
|
|
1043
|
+
while (this.readTransportRequest) {
|
|
1044
|
+
await this.readTransportRequest.catch(() => void 0);
|
|
1045
|
+
this.assertOpen();
|
|
1046
|
+
}
|
|
1047
|
+
const request = this.resolveReadTransport(readConcern);
|
|
1048
|
+
this.readTransportRequest = request;
|
|
1049
|
+
try {
|
|
1050
|
+
return await request;
|
|
1051
|
+
} finally {
|
|
1052
|
+
if (this.readTransportRequest === request) {
|
|
1053
|
+
this.readTransportRequest = null;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async resolveReadTransport(readConcern) {
|
|
1058
|
+
const endpoint = await this.client._getReadEndpoint(this.databaseId, readConcern);
|
|
1059
|
+
this.assertOpen();
|
|
1060
|
+
if (this.readTransport && this.currentReadUrl === endpoint) {
|
|
1061
|
+
return this.readTransport;
|
|
1062
|
+
}
|
|
1063
|
+
const nextTransport = this.client._createTransportForEndpoint(endpoint, this.databaseId);
|
|
1064
|
+
const oldTransport = this.readTransport;
|
|
1065
|
+
this.readTransport = nextTransport;
|
|
1066
|
+
this.currentReadUrl = endpoint;
|
|
1067
|
+
if (oldTransport) {
|
|
1068
|
+
oldTransport.close();
|
|
1069
|
+
}
|
|
1070
|
+
return this.readTransport;
|
|
1071
|
+
}
|
|
1072
|
+
async getWriteTransport() {
|
|
1073
|
+
this.assertOpen();
|
|
1074
|
+
while (this.writeTransportRequest) {
|
|
1075
|
+
await this.writeTransportRequest.catch(() => void 0);
|
|
1076
|
+
this.assertOpen();
|
|
1077
|
+
}
|
|
1078
|
+
const request = this.resolveWriteTransport();
|
|
1079
|
+
this.writeTransportRequest = request;
|
|
1080
|
+
try {
|
|
1081
|
+
return await request;
|
|
1082
|
+
} finally {
|
|
1083
|
+
if (this.writeTransportRequest === request) {
|
|
1084
|
+
this.writeTransportRequest = null;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
async resolveWriteTransport() {
|
|
1089
|
+
const endpoint = await this.client._getWriteEndpoint(this.databaseId);
|
|
1090
|
+
this.assertOpen();
|
|
1091
|
+
if (this.writeTransport && this.currentWriteUrl === endpoint) {
|
|
1092
|
+
return this.writeTransport;
|
|
1093
|
+
}
|
|
1094
|
+
const nextTransport = this.client._createTransportForEndpoint(endpoint, this.databaseId);
|
|
1095
|
+
const oldTransport = this.writeTransport;
|
|
1096
|
+
this.writeTransport = nextTransport;
|
|
1097
|
+
this.currentWriteUrl = endpoint;
|
|
1098
|
+
if (oldTransport) {
|
|
1099
|
+
oldTransport.close();
|
|
1100
|
+
}
|
|
1101
|
+
return this.writeTransport;
|
|
1102
|
+
}
|
|
1103
|
+
async getSubscriptionTransport(readConcern) {
|
|
1104
|
+
this.assertOpen();
|
|
1105
|
+
if (this.subscriptionTransport) {
|
|
1106
|
+
return this.subscriptionTransport;
|
|
1107
|
+
}
|
|
1108
|
+
while (this.subscriptionTransportRequest) {
|
|
1109
|
+
await this.subscriptionTransportRequest.catch(() => void 0);
|
|
1110
|
+
this.assertOpen();
|
|
1111
|
+
if (this.subscriptionTransport) {
|
|
1112
|
+
return this.subscriptionTransport;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
const request = this.resolveSubscriptionTransport(readConcern);
|
|
1116
|
+
this.subscriptionTransportRequest = request;
|
|
1117
|
+
try {
|
|
1118
|
+
return await request;
|
|
1119
|
+
} finally {
|
|
1120
|
+
if (this.subscriptionTransportRequest === request) {
|
|
1121
|
+
this.subscriptionTransportRequest = null;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
async resolveSubscriptionTransport(readConcern) {
|
|
1126
|
+
if (this.subscriptionTransport) {
|
|
1127
|
+
return this.subscriptionTransport;
|
|
1128
|
+
}
|
|
1129
|
+
const endpoint = await this.client._getReadEndpoint(this.databaseId, readConcern);
|
|
1130
|
+
this.assertOpen();
|
|
1131
|
+
if (this.subscriptionTransport) {
|
|
1132
|
+
return this.subscriptionTransport;
|
|
1133
|
+
}
|
|
1134
|
+
this.subscriptionTransport = this.client._createTransportForEndpoint(endpoint, this.databaseId);
|
|
1135
|
+
this.currentSubscriptionUrl = endpoint;
|
|
1136
|
+
return this.subscriptionTransport;
|
|
1137
|
+
}
|
|
1138
|
+
closeSubscriptionTransport() {
|
|
1139
|
+
if (this.subscriptionTransport) {
|
|
1140
|
+
this.subscriptionTransport.close();
|
|
1141
|
+
this.subscriptionTransport = null;
|
|
1142
|
+
}
|
|
1143
|
+
this.currentSubscriptionUrl = "";
|
|
1144
|
+
}
|
|
1145
|
+
async withSubscriptionOperation(operation) {
|
|
1146
|
+
const previous = this.subscriptionOperation;
|
|
1147
|
+
let release = () => {
|
|
1148
|
+
};
|
|
1149
|
+
this.subscriptionOperation = new Promise((resolve) => {
|
|
1150
|
+
release = resolve;
|
|
475
1151
|
});
|
|
1152
|
+
await previous.catch(() => void 0);
|
|
1153
|
+
try {
|
|
1154
|
+
return await operation();
|
|
1155
|
+
} finally {
|
|
1156
|
+
release();
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
assertOpen() {
|
|
1160
|
+
if (this.closed) {
|
|
1161
|
+
throw new RemoteError("TRANSPORT_ERROR", "Transport is closed");
|
|
1162
|
+
}
|
|
476
1163
|
}
|
|
477
1164
|
};
|
|
1165
|
+
function toSubscriptionRoutingError(err, databaseId) {
|
|
1166
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1167
|
+
return new RemoteError(
|
|
1168
|
+
"ROUTING_ERROR",
|
|
1169
|
+
`Could not re-establish active subscriptions on refreshed routing for database '${databaseId}': ${detail}`
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
function shouldRefreshRouting(err) {
|
|
1173
|
+
if (!(err instanceof RemoteError)) {
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
return err.code === "STALE_PRIMARY" || err.code === "AUTHORITY_LOST" || err.code === "COORDINATOR_UNAVAILABLE" || err.code === "NO_SAFE_PRIMARY" || err.code === "CONNECTION_ERROR";
|
|
1177
|
+
}
|
|
478
1178
|
|
|
479
1179
|
export { HttpTransport, RemoteDatabase, RemoteError, RemoteSubscriptionBuilderImpl, SirannonClient, WebSocketTransport };
|