@rsdk/db 5.12.0-next.7 → 5.12.0-next.9

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.
Files changed (35) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +10 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/multi-host-url/exception.d.ts +18 -0
  5. package/dist/multi-host-url/exception.js +48 -0
  6. package/dist/multi-host-url/exception.js.map +1 -0
  7. package/dist/multi-host-url/index.d.ts +10 -0
  8. package/dist/multi-host-url/index.js +18 -0
  9. package/dist/multi-host-url/index.js.map +1 -0
  10. package/dist/multi-host-url/multi-host-url.d.ts +37 -0
  11. package/dist/multi-host-url/multi-host-url.js +58 -0
  12. package/dist/multi-host-url/multi-host-url.js.map +1 -0
  13. package/dist/multi-host-url/multi-host-url.parser.d.ts +26 -0
  14. package/dist/multi-host-url/multi-host-url.parser.js +151 -0
  15. package/dist/multi-host-url/multi-host-url.parser.js.map +1 -0
  16. package/dist/multi-host-url/multi-host-url.resolver.d.ts +25 -0
  17. package/dist/multi-host-url/multi-host-url.resolver.js +50 -0
  18. package/dist/multi-host-url/multi-host-url.resolver.js.map +1 -0
  19. package/dist/multi-host-url/pg-native.d.ts +54 -0
  20. package/dist/multi-host-url/pg-native.js +90 -0
  21. package/dist/multi-host-url/pg-native.js.map +1 -0
  22. package/dist/multi-host-url/resolve-db-connection.d.ts +64 -0
  23. package/dist/multi-host-url/resolve-db-connection.js +57 -0
  24. package/dist/multi-host-url/resolve-db-connection.js.map +1 -0
  25. package/jest.config.unit.js +1 -0
  26. package/package.json +3 -2
  27. package/src/index.ts +18 -0
  28. package/src/multi-host-url/exception.ts +48 -0
  29. package/src/multi-host-url/index.ts +17 -0
  30. package/src/multi-host-url/multi-host-url.parser.spec.ts +131 -0
  31. package/src/multi-host-url/multi-host-url.parser.ts +182 -0
  32. package/src/multi-host-url/multi-host-url.resolver.ts +59 -0
  33. package/src/multi-host-url/multi-host-url.ts +85 -0
  34. package/src/multi-host-url/pg-native.ts +133 -0
  35. package/src/multi-host-url/resolve-db-connection.ts +127 -0
package/dist/index.d.ts CHANGED
@@ -7,3 +7,5 @@ export { Propagation } from './propagation.enum';
7
7
  export { HEALTH_CHECK_QUERY } from './constants';
8
8
  export { getSecureContextOptions } from './tls';
9
9
  export { SslModeEnum } from './ssl-mode.enum';
10
+ export { MultiHostUrl, MultiHostUrlParser, MultiHostResolver, InvalidMultiHostUrl, InvalidMultiHostProtocol, MultiHostNativeRequired, pgProbe, resolveDbConnection, } from './multi-host-url';
11
+ export type { MultiHostEntry, MultiHostUrlInit, MultiHostProbe, PgNativeModule, ResolveDbConnectionOptions, ResolvedDbConnection, } from './multi-host-url';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SslModeEnum = exports.getSecureContextOptions = exports.HEALTH_CHECK_QUERY = exports.Propagation = exports.TransactionRunner = exports.IncompatibleIsolationLevels = exports.CallOutOfContextWithMandatory = exports.NeverRunningInTransaction = exports.BaseContext = exports.ContextStorage = void 0;
3
+ exports.resolveDbConnection = exports.pgProbe = exports.MultiHostNativeRequired = exports.InvalidMultiHostProtocol = exports.InvalidMultiHostUrl = exports.MultiHostResolver = exports.MultiHostUrlParser = exports.MultiHostUrl = exports.SslModeEnum = exports.getSecureContextOptions = exports.HEALTH_CHECK_QUERY = exports.Propagation = exports.TransactionRunner = exports.IncompatibleIsolationLevels = exports.CallOutOfContextWithMandatory = exports.NeverRunningInTransaction = exports.BaseContext = exports.ContextStorage = void 0;
4
4
  var context_storage_1 = require("./context.storage");
5
5
  Object.defineProperty(exports, "ContextStorage", { enumerable: true, get: function () { return context_storage_1.ContextStorage; } });
6
6
  var context_base_1 = require("./context.base");
@@ -19,4 +19,13 @@ var tls_1 = require("./tls");
19
19
  Object.defineProperty(exports, "getSecureContextOptions", { enumerable: true, get: function () { return tls_1.getSecureContextOptions; } });
20
20
  var ssl_mode_enum_1 = require("./ssl-mode.enum");
21
21
  Object.defineProperty(exports, "SslModeEnum", { enumerable: true, get: function () { return ssl_mode_enum_1.SslModeEnum; } });
22
+ var multi_host_url_1 = require("./multi-host-url");
23
+ Object.defineProperty(exports, "MultiHostUrl", { enumerable: true, get: function () { return multi_host_url_1.MultiHostUrl; } });
24
+ Object.defineProperty(exports, "MultiHostUrlParser", { enumerable: true, get: function () { return multi_host_url_1.MultiHostUrlParser; } });
25
+ Object.defineProperty(exports, "MultiHostResolver", { enumerable: true, get: function () { return multi_host_url_1.MultiHostResolver; } });
26
+ Object.defineProperty(exports, "InvalidMultiHostUrl", { enumerable: true, get: function () { return multi_host_url_1.InvalidMultiHostUrl; } });
27
+ Object.defineProperty(exports, "InvalidMultiHostProtocol", { enumerable: true, get: function () { return multi_host_url_1.InvalidMultiHostProtocol; } });
28
+ Object.defineProperty(exports, "MultiHostNativeRequired", { enumerable: true, get: function () { return multi_host_url_1.MultiHostNativeRequired; } });
29
+ Object.defineProperty(exports, "pgProbe", { enumerable: true, get: function () { return multi_host_url_1.pgProbe; } });
30
+ Object.defineProperty(exports, "resolveDbConnection", { enumerable: true, get: function () { return multi_host_url_1.resolveDbConnection; } });
22
31
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qDAAmD;AAA1C,iHAAA,cAAc,OAAA;AACvB,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AAEpB,2CAIsB;AAHpB,uHAAA,yBAAyB,OAAA;AACzB,2HAAA,6BAA6B,OAAA;AAC7B,yHAAA,2BAA2B,OAAA;AAE7B,+DAA2D;AAAlD,yHAAA,iBAAiB,OAAA;AAC1B,uDAAiD;AAAxC,+GAAA,WAAW,OAAA;AACpB,yCAAiD;AAAxC,+GAAA,kBAAkB,OAAA;AAC3B,6BAAgD;AAAvC,8GAAA,uBAAuB,OAAA;AAChC,iDAA8C;AAArC,4GAAA,WAAW,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qDAAmD;AAA1C,iHAAA,cAAc,OAAA;AACvB,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AAEpB,2CAIsB;AAHpB,uHAAA,yBAAyB,OAAA;AACzB,2HAAA,6BAA6B,OAAA;AAC7B,yHAAA,2BAA2B,OAAA;AAE7B,+DAA2D;AAAlD,yHAAA,iBAAiB,OAAA;AAC1B,uDAAiD;AAAxC,+GAAA,WAAW,OAAA;AACpB,yCAAiD;AAAxC,+GAAA,kBAAkB,OAAA;AAC3B,6BAAgD;AAAvC,8GAAA,uBAAuB,OAAA;AAChC,iDAA8C;AAArC,4GAAA,WAAW,OAAA;AACpB,mDAS0B;AARxB,8GAAA,YAAY,OAAA;AACZ,oHAAA,kBAAkB,OAAA;AAClB,mHAAA,iBAAiB,OAAA;AACjB,qHAAA,mBAAmB,OAAA;AACnB,0HAAA,wBAAwB,OAAA;AACxB,yHAAA,uBAAuB,OAAA;AACvB,yGAAA,OAAO,OAAA;AACP,qHAAA,mBAAmB,OAAA"}
@@ -0,0 +1,18 @@
1
+ import { BootstrapException } from '@rsdk/core';
2
+ export declare class InvalidMultiHostUrl extends BootstrapException {
3
+ constructor(str: string, cause?: unknown);
4
+ }
5
+ export declare class InvalidMultiHostProtocol extends BootstrapException {
6
+ constructor(expectedProtocols: string[], protocol: string, url: string);
7
+ }
8
+ /**
9
+ * Multi-host URL задан, но `pg-native` не установлен — runtime failover
10
+ * через libpq невозможен. По умолчанию `resolveDbConnection` падает на
11
+ * старте, чтобы single-host pool не выдавался под обещание failover,
12
+ * которого не будет. Чтобы разрешить тихий фолбэк на bootstrap-resolve,
13
+ * передайте в `resolveDbConnection` поле `bootstrapProbe` — в плагинах
14
+ * `@rsdk/db.*` это управляется env-переменной `DB_MULTIHOST_BOOTSTRAP_FALLBACK`.
15
+ */
16
+ export declare class MultiHostNativeRequired extends BootstrapException {
17
+ constructor(hostsCount: number);
18
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MultiHostNativeRequired = exports.InvalidMultiHostProtocol = exports.InvalidMultiHostUrl = void 0;
4
+ const core_1 = require("@rsdk/core");
5
+ /**
6
+ * Заменяет `userinfo` (`user:pass@`) в URL-подобной строке на `***:***@`,
7
+ * чтобы пароль из `DB_URL` не попадал в логи через message/details ошибки.
8
+ */
9
+ const maskUrlCredentials = (str) => str.replace(/(:\/\/)[^/@\s]+@/u, '$1***:***@');
10
+ class InvalidMultiHostUrl extends core_1.BootstrapException {
11
+ constructor(str, cause) {
12
+ super(`Invalid multi-host url string: <${maskUrlCredentials(str)}>`, {
13
+ cause,
14
+ });
15
+ }
16
+ }
17
+ exports.InvalidMultiHostUrl = InvalidMultiHostUrl;
18
+ class InvalidMultiHostProtocol extends core_1.BootstrapException {
19
+ constructor(expectedProtocols, protocol, url) {
20
+ super(`Invalid protocol: <${protocol}>, expected: ${expectedProtocols}`, {
21
+ details: {
22
+ protocol,
23
+ url: maskUrlCredentials(url),
24
+ expected: expectedProtocols,
25
+ },
26
+ });
27
+ }
28
+ }
29
+ exports.InvalidMultiHostProtocol = InvalidMultiHostProtocol;
30
+ /**
31
+ * Multi-host URL задан, но `pg-native` не установлен — runtime failover
32
+ * через libpq невозможен. По умолчанию `resolveDbConnection` падает на
33
+ * старте, чтобы single-host pool не выдавался под обещание failover,
34
+ * которого не будет. Чтобы разрешить тихий фолбэк на bootstrap-resolve,
35
+ * передайте в `resolveDbConnection` поле `bootstrapProbe` — в плагинах
36
+ * `@rsdk/db.*` это управляется env-переменной `DB_MULTIHOST_BOOTSTRAP_FALLBACK`.
37
+ */
38
+ class MultiHostNativeRequired extends core_1.BootstrapException {
39
+ constructor(hostsCount) {
40
+ super(`multi-host db: \`pg-native\` is not installed but URL has ${hostsCount} hosts. ` +
41
+ 'Install `pg-native` (and a system libpq) for libpq runtime failover, ' +
42
+ 'or pass `bootstrapProbe` to resolveDbConnection to allow falling back ' +
43
+ 'to bootstrap-resolve (no runtime failover). In @rsdk/db.* plugins this ' +
44
+ 'is controlled by DB_MULTIHOST_BOOTSTRAP_FALLBACK=true.');
45
+ }
46
+ }
47
+ exports.MultiHostNativeRequired = MultiHostNativeRequired;
48
+ //# sourceMappingURL=exception.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exception.js","sourceRoot":"","sources":["../../src/multi-host-url/exception.ts"],"names":[],"mappings":";;;AAAA,qCAAgD;AAEhD;;;GAGG;AACH,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAAU,EAAE,CACjD,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,YAAY,CAAC,CAAC;AAEjD,MAAa,mBAAoB,SAAQ,yBAAkB;IACzD,YAAY,GAAW,EAAE,KAAe;QACtC,KAAK,CAAC,mCAAmC,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE;YACnE,KAAK;SACN,CAAC,CAAC;IACL,CAAC;CACF;AAND,kDAMC;AAED,MAAa,wBAAyB,SAAQ,yBAAkB;IAC9D,YAAY,iBAA2B,EAAE,QAAgB,EAAE,GAAW;QACpE,KAAK,CAAC,sBAAsB,QAAQ,gBAAgB,iBAAiB,EAAE,EAAE;YACvE,OAAO,EAAE;gBACP,QAAQ;gBACR,GAAG,EAAE,kBAAkB,CAAC,GAAG,CAAC;gBAC5B,QAAQ,EAAE,iBAAiB;aAC5B;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAVD,4DAUC;AAED;;;;;;;GAOG;AACH,MAAa,uBAAwB,SAAQ,yBAAkB;IAC7D,YAAY,UAAkB;QAC5B,KAAK,CACH,6DAA6D,UAAU,UAAU;YAC/E,uEAAuE;YACvE,wEAAwE;YACxE,yEAAyE;YACzE,wDAAwD,CAC3D,CAAC;IACJ,CAAC;CACF;AAVD,0DAUC"}
@@ -0,0 +1,10 @@
1
+ export { MultiHostUrl } from './multi-host-url';
2
+ export type { MultiHostEntry, MultiHostUrlInit } from './multi-host-url';
3
+ export { MultiHostUrlParser } from './multi-host-url.parser';
4
+ export { MultiHostResolver } from './multi-host-url.resolver';
5
+ export type { MultiHostProbe } from './multi-host-url.resolver';
6
+ export { InvalidMultiHostUrl, InvalidMultiHostProtocol, MultiHostNativeRequired, } from './exception';
7
+ export { pgProbe } from './pg-native';
8
+ export type { PgNativeModule } from './pg-native';
9
+ export { resolveDbConnection } from './resolve-db-connection';
10
+ export type { ResolveDbConnectionOptions, ResolvedDbConnection, } from './resolve-db-connection';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveDbConnection = exports.pgProbe = exports.MultiHostNativeRequired = exports.InvalidMultiHostProtocol = exports.InvalidMultiHostUrl = exports.MultiHostResolver = exports.MultiHostUrlParser = exports.MultiHostUrl = void 0;
4
+ var multi_host_url_1 = require("./multi-host-url");
5
+ Object.defineProperty(exports, "MultiHostUrl", { enumerable: true, get: function () { return multi_host_url_1.MultiHostUrl; } });
6
+ var multi_host_url_parser_1 = require("./multi-host-url.parser");
7
+ Object.defineProperty(exports, "MultiHostUrlParser", { enumerable: true, get: function () { return multi_host_url_parser_1.MultiHostUrlParser; } });
8
+ var multi_host_url_resolver_1 = require("./multi-host-url.resolver");
9
+ Object.defineProperty(exports, "MultiHostResolver", { enumerable: true, get: function () { return multi_host_url_resolver_1.MultiHostResolver; } });
10
+ var exception_1 = require("./exception");
11
+ Object.defineProperty(exports, "InvalidMultiHostUrl", { enumerable: true, get: function () { return exception_1.InvalidMultiHostUrl; } });
12
+ Object.defineProperty(exports, "InvalidMultiHostProtocol", { enumerable: true, get: function () { return exception_1.InvalidMultiHostProtocol; } });
13
+ Object.defineProperty(exports, "MultiHostNativeRequired", { enumerable: true, get: function () { return exception_1.MultiHostNativeRequired; } });
14
+ var pg_native_1 = require("./pg-native");
15
+ Object.defineProperty(exports, "pgProbe", { enumerable: true, get: function () { return pg_native_1.pgProbe; } });
16
+ var resolve_db_connection_1 = require("./resolve-db-connection");
17
+ Object.defineProperty(exports, "resolveDbConnection", { enumerable: true, get: function () { return resolve_db_connection_1.resolveDbConnection; } });
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/multi-host-url/index.ts"],"names":[],"mappings":";;;AAAA,mDAAgD;AAAvC,8GAAA,YAAY,OAAA;AAErB,iEAA6D;AAApD,2HAAA,kBAAkB,OAAA;AAC3B,qEAA8D;AAArD,4HAAA,iBAAiB,OAAA;AAE1B,yCAIqB;AAHnB,gHAAA,mBAAmB,OAAA;AACnB,qHAAA,wBAAwB,OAAA;AACxB,oHAAA,uBAAuB,OAAA;AAEzB,yCAAsC;AAA7B,oGAAA,OAAO,OAAA;AAEhB,iEAA8D;AAArD,4HAAA,mBAAmB,OAAA"}
@@ -0,0 +1,37 @@
1
+ export interface MultiHostEntry {
2
+ host: string;
3
+ /**
4
+ * Порт хоста, если он явно указан в URL. Если `undefined` — порт в
5
+ * исходном URL отсутствует; вызывающая сторона (ORM-плагин / драйвер) сама
6
+ * подставляет дефолтное значение для своей СУБД (`5432` для PG,
7
+ * `3306` для MySQL и т.п.).
8
+ */
9
+ port?: number;
10
+ }
11
+ export interface MultiHostUrlInit {
12
+ protocol: string;
13
+ username?: string;
14
+ password?: string;
15
+ hosts: MultiHostEntry[];
16
+ database?: string;
17
+ searchParams: URLSearchParams;
18
+ }
19
+ export declare class MultiHostUrl {
20
+ readonly protocol: string;
21
+ readonly username?: string;
22
+ readonly password?: string;
23
+ readonly hosts: readonly MultiHostEntry[];
24
+ readonly database?: string;
25
+ readonly searchParams: URLSearchParams;
26
+ constructor(init: MultiHostUrlInit);
27
+ /**
28
+ * Single-host URL для передачи в драйвер БД (pg, knex, typeorm, mikro-orm).
29
+ * Драйверы не понимают multihost-синтаксис в connection string, поэтому
30
+ * перед бутстрапом нужно выбрать один живой хост из списка.
31
+ */
32
+ getHostAt(index: number): URL;
33
+ /**
34
+ * Возвращает оригинальный multi-host URL (или single-host, если хост один).
35
+ */
36
+ toString(): string;
37
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MultiHostUrl = void 0;
4
+ class MultiHostUrl {
5
+ protocol;
6
+ username;
7
+ password;
8
+ hosts;
9
+ database;
10
+ searchParams;
11
+ constructor(init) {
12
+ this.protocol = init.protocol;
13
+ this.hosts = init.hosts;
14
+ this.searchParams = init.searchParams;
15
+ if (init.username !== undefined)
16
+ this.username = init.username;
17
+ if (init.password !== undefined)
18
+ this.password = init.password;
19
+ if (init.database !== undefined)
20
+ this.database = init.database;
21
+ }
22
+ /**
23
+ * Single-host URL для передачи в драйвер БД (pg, knex, typeorm, mikro-orm).
24
+ * Драйверы не понимают multihost-синтаксис в connection string, поэтому
25
+ * перед бутстрапом нужно выбрать один живой хост из списка.
26
+ */
27
+ getHostAt(index) {
28
+ const entry = this.hosts[index];
29
+ if (!entry) {
30
+ throw new RangeError(`host index ${index} out of bounds (hosts.length=${this.hosts.length})`);
31
+ }
32
+ const auth = this.username
33
+ ? `${encodeURIComponent(this.username)}${this.password ? `:${encodeURIComponent(this.password)}` : ''}@`
34
+ : '';
35
+ const search = this.searchParams.toString();
36
+ const query = search ? `?${search}` : '';
37
+ const database = this.database ? `/${this.database}` : '';
38
+ const port = entry.port === undefined ? '' : `:${entry.port}`;
39
+ return new URL(`${this.protocol}//${auth}${entry.host}${port}${database}${query}`);
40
+ }
41
+ /**
42
+ * Возвращает оригинальный multi-host URL (или single-host, если хост один).
43
+ */
44
+ toString() {
45
+ const auth = this.username
46
+ ? `${encodeURIComponent(this.username)}${this.password ? `:${encodeURIComponent(this.password)}` : ''}@`
47
+ : '';
48
+ const hosts = this.hosts
49
+ .map((entry) => entry.port === undefined ? entry.host : `${entry.host}:${entry.port}`)
50
+ .join(',');
51
+ const search = this.searchParams.toString();
52
+ const query = search ? `?${search}` : '';
53
+ const database = this.database ? `/${this.database}` : '';
54
+ return `${this.protocol}//${auth}${hosts}${database}${query}`;
55
+ }
56
+ }
57
+ exports.MultiHostUrl = MultiHostUrl;
58
+ //# sourceMappingURL=multi-host-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-host-url.js","sourceRoot":"","sources":["../../src/multi-host-url/multi-host-url.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { PropertyParser } from '@rsdk/core';
2
+ import { MultiHostUrl } from './multi-host-url';
3
+ export declare class MultiHostUrlParser implements PropertyParser<MultiHostUrl> {
4
+ private readonly protocols;
5
+ constructor(...protocols: string[]);
6
+ type(): string;
7
+ description(): string;
8
+ parse(value: unknown): MultiHostUrl;
9
+ /**
10
+ * Разбивает строку на три части: то, что слева от списка хостов
11
+ * (`scheme://[userinfo@]`), сам список (`h1:5432,h2:5432,...`) и то,
12
+ * что справа (`/db?params`). Делается вручную, потому что multi-host
13
+ * URI ломает `new URL()` ещё до того, как мы дойдём до парсинга хостов.
14
+ * Дальше вызывающая сторона подставляет в `hostsSegment` placeholder и
15
+ * скармливает результат `new URL()` уже как валидный single-host URI.
16
+ */
17
+ private getSplittedOnHosts;
18
+ /**
19
+ * Возвращает имя БД из pathname (`/db` → `db`). Пустой pathname (URL без
20
+ * пути) даёт `''` — вызывающая сторона трактует это как «database не задан».
21
+ */
22
+ private extractDatabase;
23
+ private findPathStart;
24
+ private parseHosts;
25
+ private assertProtocol;
26
+ }
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MultiHostUrlParser = void 0;
4
+ const common_1 = require("@rsdk/common");
5
+ const exception_1 = require("./exception");
6
+ const multi_host_url_1 = require("./multi-host-url");
7
+ const PLACEHOLDER_HOST = '__rsdk_multihost_placeholder__';
8
+ class MultiHostUrlParser {
9
+ protocols;
10
+ constructor(...protocols) {
11
+ this.protocols = protocols;
12
+ }
13
+ type() {
14
+ return 'MultiHostUrl';
15
+ }
16
+ description() {
17
+ return 'Parses string into MultiHostUrl supporting <scheme>://user:pass@host1:port1,host2:port2/db?... syntax';
18
+ }
19
+ parse(value) {
20
+ try {
21
+ common_1.Assert.isString(value);
22
+ const splitted = this.getSplittedOnHosts(value);
23
+ const placeholder = `${splitted.beforeHosts}${PLACEHOLDER_HOST}${splitted.afterHosts}`;
24
+ const url = new URL(placeholder);
25
+ this.assertProtocol(url.protocol, value);
26
+ const hosts = this.parseHosts(splitted.hostsSegment, value);
27
+ const database = this.extractDatabase(url);
28
+ return new multi_host_url_1.MultiHostUrl({
29
+ protocol: url.protocol,
30
+ ...(url.username && {
31
+ username: decodeURIComponent(url.username),
32
+ }),
33
+ ...(url.password && {
34
+ password: decodeURIComponent(url.password),
35
+ }),
36
+ hosts,
37
+ ...(database && { database }),
38
+ searchParams: url.searchParams,
39
+ });
40
+ }
41
+ catch (error) {
42
+ if (error instanceof exception_1.InvalidMultiHostUrl ||
43
+ error instanceof exception_1.InvalidMultiHostProtocol) {
44
+ throw error;
45
+ }
46
+ throw new exception_1.InvalidMultiHostUrl(String(value), error);
47
+ }
48
+ }
49
+ /**
50
+ * Разбивает строку на три части: то, что слева от списка хостов
51
+ * (`scheme://[userinfo@]`), сам список (`h1:5432,h2:5432,...`) и то,
52
+ * что справа (`/db?params`). Делается вручную, потому что multi-host
53
+ * URI ломает `new URL()` ещё до того, как мы дойдём до парсинга хостов.
54
+ * Дальше вызывающая сторона подставляет в `hostsSegment` placeholder и
55
+ * скармливает результат `new URL()` уже как валидный single-host URI.
56
+ */
57
+ getSplittedOnHosts(value) {
58
+ // scheme заканчивается на `://`. Без него это не URI.
59
+ const schemeEnd = value.indexOf('://');
60
+ if (schemeEnd === -1) {
61
+ throw new exception_1.InvalidMultiHostUrl(value);
62
+ }
63
+ // Authority — всё между `://` и следующим `/`, `?` или `#`.
64
+ // Внутри authority может быть `userinfo@hosts`.
65
+ const authorityStart = schemeEnd + 3;
66
+ const pathStart = this.findPathStart(value, authorityStart);
67
+ const authority = value.slice(authorityStart, pathStart);
68
+ const after = value.slice(pathStart);
69
+ // `userinfo` отделяется от hosts последним `@`. `lastIndexOf` —
70
+ // на случай, если в пароле встретится сам символ `@` (хотя по
71
+ // спеке его положено percent-кодировать, на практике встречается).
72
+ const atIdx = authority.lastIndexOf('@');
73
+ const userInfo = atIdx === -1 ? '' : authority.slice(0, atIdx + 1);
74
+ const hostsSegment = atIdx === -1 ? authority : authority.slice(atIdx + 1);
75
+ if (!hostsSegment) {
76
+ throw new exception_1.InvalidMultiHostUrl(value);
77
+ }
78
+ // `beforeHosts` сохраняем вместе с userInfo — placeholder будет
79
+ // подставлен только вместо hosts, чтобы `new URL()` корректно
80
+ // распарсил scheme/userinfo/path/query.
81
+ return {
82
+ beforeHosts: value.slice(0, authorityStart) + userInfo,
83
+ hostsSegment,
84
+ afterHosts: after,
85
+ };
86
+ }
87
+ /**
88
+ * Возвращает имя БД из pathname (`/db` → `db`). Пустой pathname (URL без
89
+ * пути) даёт `''` — вызывающая сторона трактует это как «database не задан».
90
+ */
91
+ extractDatabase(url) {
92
+ return url.pathname.replace(/^\//, '');
93
+ }
94
+ findPathStart(value, from) {
95
+ for (let i = from; i < value.length; i++) {
96
+ const ch = value[i];
97
+ if (ch === '/' || ch === '?' || ch === '#') {
98
+ return i;
99
+ }
100
+ }
101
+ return value.length;
102
+ }
103
+ parseHosts(segment, original) {
104
+ const parts = segment.split(',');
105
+ const hosts = [];
106
+ for (const part of parts) {
107
+ const trimmed = part.trim();
108
+ if (!trimmed) {
109
+ throw new exception_1.InvalidMultiHostUrl(original);
110
+ }
111
+ const colonIdx = trimmed.lastIndexOf(':');
112
+ let host;
113
+ let port;
114
+ if (colonIdx === -1) {
115
+ // Порт отсутствует — оставляем undefined; default подставит вызывающая сторона.
116
+ host = trimmed;
117
+ port = undefined;
118
+ }
119
+ else {
120
+ host = trimmed.slice(0, colonIdx);
121
+ const portStr = trimmed.slice(colonIdx + 1);
122
+ const parsed = Number(portStr);
123
+ if (portStr.length === 0 ||
124
+ !Number.isInteger(parsed) ||
125
+ parsed < 1 ||
126
+ parsed > 65535) {
127
+ throw new exception_1.InvalidMultiHostUrl(original);
128
+ }
129
+ port = parsed;
130
+ }
131
+ if (!host) {
132
+ throw new exception_1.InvalidMultiHostUrl(original);
133
+ }
134
+ hosts.push(port === undefined ? { host } : { host, port });
135
+ }
136
+ if (hosts.length === 0) {
137
+ throw new exception_1.InvalidMultiHostUrl(original);
138
+ }
139
+ return hosts;
140
+ }
141
+ assertProtocol(protocol, original) {
142
+ if (this.protocols.length === 0) {
143
+ return;
144
+ }
145
+ if (!this.protocols.includes(protocol)) {
146
+ throw new exception_1.InvalidMultiHostProtocol(this.protocols, protocol, original);
147
+ }
148
+ }
149
+ }
150
+ exports.MultiHostUrlParser = MultiHostUrlParser;
151
+ //# sourceMappingURL=multi-host-url.parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-host-url.parser.js","sourceRoot":"","sources":["../../src/multi-host-url/multi-host-url.parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { ILogger } from '@rsdk/logging';
2
+ import type { MultiHostUrl } from './multi-host-url';
3
+ export type MultiHostProbe = (candidate: URL) => Promise<void>;
4
+ /**
5
+ * Стратегия "resolve at bootstrap": перебирает хосты из multi-host URL и
6
+ * выбирает первый, на котором проба прошла успешно. Если ни один не отвечает,
7
+ * возвращается первый хост — далее у каждого ORM работает свой reconnect-цикл.
8
+ *
9
+ * **Bootstrap-only failover.** Чистый JS-драйвер `pg` не реализует
10
+ * libpq-style failover, поэтому при использовании этого resolver-а
11
+ * переключение между хостами происходит **только** в момент старта.
12
+ * Если уже выбранный хост упадёт в runtime, reconnect-цикл ORM продолжит
13
+ * подключаться к нему же — до перезапуска приложения.
14
+ *
15
+ * Для **runtime failover** (libpq-style) используйте {@link resolveDbConnection}
16
+ * — он автоматически переключается на native-libpq путь, когда хостов >1
17
+ * и доступен `pg-native`. `MultiHostResolver` остаётся низкоуровневым
18
+ * примитивом для случаев, где native путь невозможен (не-postgres драйверы)
19
+ * или нежелателен (например, чтобы не зависеть от системной libpq).
20
+ */
21
+ export declare class MultiHostResolver {
22
+ private readonly logger;
23
+ constructor(logger: ILogger);
24
+ resolve(url: MultiHostUrl, probe: MultiHostProbe): Promise<URL>;
25
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MultiHostResolver = void 0;
4
+ /**
5
+ * Стратегия "resolve at bootstrap": перебирает хосты из multi-host URL и
6
+ * выбирает первый, на котором проба прошла успешно. Если ни один не отвечает,
7
+ * возвращается первый хост — далее у каждого ORM работает свой reconnect-цикл.
8
+ *
9
+ * **Bootstrap-only failover.** Чистый JS-драйвер `pg` не реализует
10
+ * libpq-style failover, поэтому при использовании этого resolver-а
11
+ * переключение между хостами происходит **только** в момент старта.
12
+ * Если уже выбранный хост упадёт в runtime, reconnect-цикл ORM продолжит
13
+ * подключаться к нему же — до перезапуска приложения.
14
+ *
15
+ * Для **runtime failover** (libpq-style) используйте {@link resolveDbConnection}
16
+ * — он автоматически переключается на native-libpq путь, когда хостов >1
17
+ * и доступен `pg-native`. `MultiHostResolver` остаётся низкоуровневым
18
+ * примитивом для случаев, где native путь невозможен (не-postgres драйверы)
19
+ * или нежелателен (например, чтобы не зависеть от системной libpq).
20
+ */
21
+ class MultiHostResolver {
22
+ logger;
23
+ constructor(logger) {
24
+ this.logger = logger;
25
+ }
26
+ async resolve(url, probe) {
27
+ if (url.hosts.length === 1) {
28
+ return url.getHostAt(0);
29
+ }
30
+ for (let i = 0; i < url.hosts.length; i++) {
31
+ const candidate = url.getHostAt(i);
32
+ const entry = url.hosts[i];
33
+ const hostLabel = entry.port === undefined ? entry.host : `${entry.host}:${entry.port}`;
34
+ try {
35
+ await probe(candidate);
36
+ if (i > 0) {
37
+ this.logger.info(`multi-host db: connected to host #${i + 1} (${hostLabel}) after previous hosts failed`);
38
+ }
39
+ return candidate;
40
+ }
41
+ catch (error) {
42
+ this.logger.warn(`multi-host db: host ${hostLabel} not reachable, trying next`, error);
43
+ }
44
+ }
45
+ this.logger.warn('multi-host db: none of the configured hosts responded to the bootstrap probe; falling back to the first host and relying on reconnect cycle');
46
+ return url.getHostAt(0);
47
+ }
48
+ }
49
+ exports.MultiHostResolver = MultiHostResolver;
50
+ //# sourceMappingURL=multi-host-url.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-host-url.resolver.js","sourceRoot":"","sources":["../../src/multi-host-url/multi-host-url.resolver.ts"],"names":[],"mappings":";;;AAMA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,iBAAiB;IACC;IAA7B,YAA6B,MAAe;QAAf,WAAM,GAAN,MAAM,CAAS;IAAG,CAAC;IAEhD,KAAK,CAAC,OAAO,CAAC,GAAiB,EAAE,KAAqB;QACpD,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC;QAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,SAAS,GACb,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YAExE,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;gBACvB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACV,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,qCAAqC,CAAC,GAAG,CAAC,KAAK,SAAS,+BAA+B,CACxF,CAAC;gBACJ,CAAC;gBACD,OAAO,SAAS,CAAC;YACnB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,uBAAuB,SAAS,6BAA6B,EAC7D,KAAc,CACf,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,6IAA6I,CAC9I,CAAC;QACF,OAAO,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;CACF;AAnCD,8CAmCC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Утилиты для работы с `pg` и `pg.native` (libpq) на стороне @rsdk/db.
3
+ * `pg` — peer-зависимость использующего пакета; здесь оборачиваем `require`
4
+ * в try/catch, чтобы пакет, не использующий postgres, не падал при загрузке.
5
+ */
6
+ export type PgClient = {
7
+ connect(): Promise<void>;
8
+ query(text: string): Promise<unknown>;
9
+ end(): Promise<void>;
10
+ };
11
+ export type PgClientCtor = new (config: {
12
+ connectionString?: string;
13
+ /**
14
+ * Escape hatch у `pg.native`: строка идёт напрямую в libpq, минуя
15
+ * `pg-connection-string` (он парсит URL через `new URL()` и падает на
16
+ * multi-host синтаксис).
17
+ * @see node_modules/pg/lib/native/client.js
18
+ */
19
+ nativeConnectionString?: string;
20
+ ssl?: unknown;
21
+ connectionTimeoutMillis?: number;
22
+ }) => PgClient;
23
+ /**
24
+ * Объект-модуль `pg.native`. TypeORM/Knex/MikroORM принимают его как
25
+ * `driver`/`overrideConfig.driver`, поэтому signature намеренно широкий.
26
+ */
27
+ export type PgNativeModule = {
28
+ Client: PgClientCtor;
29
+ [key: string]: unknown;
30
+ };
31
+ /**
32
+ * Пробует загрузить `pg.native` (биндинги к libpq). Доступ к `pg.native`
33
+ * лениво требует пакет `pg-native`; если его нет, возвращаем `null`, чтобы
34
+ * вызывающая сторона могла переключиться на bootstrap-resolve вместо
35
+ * fatal-ошибки.
36
+ */
37
+ export declare const loadPgNative: () => PgNativeModule | null;
38
+ /**
39
+ * Bootstrap probe одного хоста: connects, runs `SELECT 1`, disconnects.
40
+ * Подходит как `bootstrapProbe` в {@link resolveDbConnection} для
41
+ * postgres-семейства драйверов.
42
+ */
43
+ export declare const pgProbe: (candidate: URL, ssl: unknown) => Promise<void>;
44
+ /**
45
+ * Bootstrap probe для multi-host URL через нативный libpq-клиент.
46
+ * libpq сам перебирает хосты внутри connection-string, поэтому достаточно
47
+ * одного `SELECT 1`: если ни один хост не доступен — упадёт здесь, fail-fast.
48
+ *
49
+ * Multi-host строка передаётся через `nativeConnectionString`, чтобы
50
+ * обойти JS-парсер `pg-connection-string` (он падает на multi-host URI).
51
+ * SSL-опции в native-режиме задаются параметрами libpq в самой строке
52
+ * (`sslmode=`, `sslrootcert=`, ...), JS-объект `ssl` не используется.
53
+ */
54
+ export declare const pgProbeMultiHost: (multiHostUrl: string, Client: PgClientCtor) => Promise<void>;