taurusdb-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +21 -0
  2. package/dist/auth/secret-resolver.d.ts +16 -0
  3. package/dist/auth/secret-resolver.js +64 -0
  4. package/dist/auth/sql-profile-loader/env-source.d.ts +3 -0
  5. package/dist/auth/sql-profile-loader/env-source.js +94 -0
  6. package/dist/auth/sql-profile-loader/file-source.d.ts +6 -0
  7. package/dist/auth/sql-profile-loader/file-source.js +40 -0
  8. package/dist/auth/sql-profile-loader/loader.d.ts +16 -0
  9. package/dist/auth/sql-profile-loader/loader.js +81 -0
  10. package/dist/auth/sql-profile-loader/parsing.d.ts +14 -0
  11. package/dist/auth/sql-profile-loader/parsing.js +216 -0
  12. package/dist/auth/sql-profile-loader/runtime-override.d.ts +14 -0
  13. package/dist/auth/sql-profile-loader/runtime-override.js +52 -0
  14. package/dist/auth/sql-profile-loader/types.d.ts +64 -0
  15. package/dist/auth/sql-profile-loader/types.js +1 -0
  16. package/dist/auth/sql-profile-loader.d.ts +4 -0
  17. package/dist/auth/sql-profile-loader.js +3 -0
  18. package/dist/capability/feature-matrix.d.ts +5 -0
  19. package/dist/capability/feature-matrix.js +237 -0
  20. package/dist/capability/probe.d.ts +19 -0
  21. package/dist/capability/probe.js +139 -0
  22. package/dist/capability/types.d.ts +49 -0
  23. package/dist/capability/types.js +16 -0
  24. package/dist/capability/version.d.ts +3 -0
  25. package/dist/capability/version.js +47 -0
  26. package/dist/cloud/auth.d.ts +26 -0
  27. package/dist/cloud/auth.js +198 -0
  28. package/dist/cloud/instances.d.ts +46 -0
  29. package/dist/cloud/instances.js +224 -0
  30. package/dist/config/env.d.ts +1 -0
  31. package/dist/config/env.js +194 -0
  32. package/dist/config/index.d.ts +6 -0
  33. package/dist/config/index.js +21 -0
  34. package/dist/config/redaction.d.ts +2 -0
  35. package/dist/config/redaction.js +19 -0
  36. package/dist/config/schema.d.ts +417 -0
  37. package/dist/config/schema.js +100 -0
  38. package/dist/context/datasource-resolver.d.ts +19 -0
  39. package/dist/context/datasource-resolver.js +71 -0
  40. package/dist/context/session-context.d.ts +26 -0
  41. package/dist/context/session-context.js +1 -0
  42. package/dist/diagnostics/metrics-source.d.ts +65 -0
  43. package/dist/diagnostics/metrics-source.js +280 -0
  44. package/dist/diagnostics/slow-sql-source/das-source.d.ts +43 -0
  45. package/dist/diagnostics/slow-sql-source/das-source.js +170 -0
  46. package/dist/diagnostics/slow-sql-source/factory.d.ts +5 -0
  47. package/dist/diagnostics/slow-sql-source/factory.js +87 -0
  48. package/dist/diagnostics/slow-sql-source/parsers.d.ts +7 -0
  49. package/dist/diagnostics/slow-sql-source/parsers.js +125 -0
  50. package/dist/diagnostics/slow-sql-source/taurus-api-source.d.ts +42 -0
  51. package/dist/diagnostics/slow-sql-source/taurus-api-source.js +149 -0
  52. package/dist/diagnostics/slow-sql-source/types.d.ts +40 -0
  53. package/dist/diagnostics/slow-sql-source/types.js +1 -0
  54. package/dist/diagnostics/slow-sql-source/utils.d.ts +20 -0
  55. package/dist/diagnostics/slow-sql-source/utils.js +170 -0
  56. package/dist/diagnostics/slow-sql-source.d.ts +4 -0
  57. package/dist/diagnostics/slow-sql-source.js +3 -0
  58. package/dist/diagnostics/types.d.ts +189 -0
  59. package/dist/diagnostics/types.js +39 -0
  60. package/dist/engine/data-access/locks.d.ts +8 -0
  61. package/dist/engine/data-access/locks.js +146 -0
  62. package/dist/engine/data-access/processlist.d.ts +4 -0
  63. package/dist/engine/data-access/processlist.js +56 -0
  64. package/dist/engine/data-access/statements.d.ts +10 -0
  65. package/dist/engine/data-access/statements.js +203 -0
  66. package/dist/engine/data-access/storage.d.ts +6 -0
  67. package/dist/engine/data-access/storage.js +96 -0
  68. package/dist/engine/data-access.d.ts +4 -0
  69. package/dist/engine/data-access.js +4 -0
  70. package/dist/engine/diagnostics.d.ts +7 -0
  71. package/dist/engine/diagnostics.js +7 -0
  72. package/dist/engine/helper-modules/diagnostics.d.ts +57 -0
  73. package/dist/engine/helper-modules/diagnostics.js +322 -0
  74. package/dist/engine/helper-modules/parsers.d.ts +13 -0
  75. package/dist/engine/helper-modules/parsers.js +283 -0
  76. package/dist/engine/helper-modules/sql.d.ts +12 -0
  77. package/dist/engine/helper-modules/sql.js +119 -0
  78. package/dist/engine/helper-modules/types.d.ts +103 -0
  79. package/dist/engine/helper-modules/types.js +1 -0
  80. package/dist/engine/helpers.d.ts +4 -0
  81. package/dist/engine/helpers.js +4 -0
  82. package/dist/engine/runtime.d.ts +20 -0
  83. package/dist/engine/runtime.js +385 -0
  84. package/dist/engine/types.d.ts +125 -0
  85. package/dist/engine/types.js +1 -0
  86. package/dist/engine/workflows/connection-spike.d.ts +4 -0
  87. package/dist/engine/workflows/connection-spike.js +316 -0
  88. package/dist/engine/workflows/db-hotspot.d.ts +4 -0
  89. package/dist/engine/workflows/db-hotspot.js +182 -0
  90. package/dist/engine/workflows/lock-contention-helpers/entities.d.ts +9 -0
  91. package/dist/engine/workflows/lock-contention-helpers/entities.js +58 -0
  92. package/dist/engine/workflows/lock-contention-helpers/no-match.d.ts +3 -0
  93. package/dist/engine/workflows/lock-contention-helpers/no-match.js +65 -0
  94. package/dist/engine/workflows/lock-contention-helpers/report.d.ts +21 -0
  95. package/dist/engine/workflows/lock-contention-helpers/report.js +104 -0
  96. package/dist/engine/workflows/lock-contention-helpers/root-cause.d.ts +4 -0
  97. package/dist/engine/workflows/lock-contention-helpers/root-cause.js +79 -0
  98. package/dist/engine/workflows/lock-contention-helpers/signals.d.ts +22 -0
  99. package/dist/engine/workflows/lock-contention-helpers/signals.js +34 -0
  100. package/dist/engine/workflows/lock-contention-helpers.d.ts +5 -0
  101. package/dist/engine/workflows/lock-contention-helpers.js +5 -0
  102. package/dist/engine/workflows/lock-contention.d.ts +4 -0
  103. package/dist/engine/workflows/lock-contention.js +67 -0
  104. package/dist/engine/workflows/service-latency.d.ts +4 -0
  105. package/dist/engine/workflows/service-latency.js +262 -0
  106. package/dist/engine/workflows/slow-query-helpers.d.ts +41 -0
  107. package/dist/engine/workflows/slow-query-helpers.js +253 -0
  108. package/dist/engine/workflows/slow-query.d.ts +4 -0
  109. package/dist/engine/workflows/slow-query.js +156 -0
  110. package/dist/engine/workflows/storage-pressure-helpers.d.ts +12 -0
  111. package/dist/engine/workflows/storage-pressure-helpers.js +281 -0
  112. package/dist/engine/workflows/storage-pressure.d.ts +4 -0
  113. package/dist/engine/workflows/storage-pressure.js +27 -0
  114. package/dist/engine/workflows/top-slow-sql.d.ts +4 -0
  115. package/dist/engine/workflows/top-slow-sql.js +222 -0
  116. package/dist/engine.d.ts +77 -0
  117. package/dist/engine.js +240 -0
  118. package/dist/executor/adapters/mysql.d.ts +2 -0
  119. package/dist/executor/adapters/mysql.js +114 -0
  120. package/dist/executor/connection-pool.d.ts +105 -0
  121. package/dist/executor/connection-pool.js +236 -0
  122. package/dist/executor/explain.d.ts +5 -0
  123. package/dist/executor/explain.js +119 -0
  124. package/dist/executor/query-tracker.d.ts +45 -0
  125. package/dist/executor/query-tracker.js +83 -0
  126. package/dist/executor/result-normalizer.d.ts +6 -0
  127. package/dist/executor/result-normalizer.js +47 -0
  128. package/dist/executor/sql-executor.d.ts +32 -0
  129. package/dist/executor/sql-executor.js +250 -0
  130. package/dist/executor/types.d.ts +70 -0
  131. package/dist/executor/types.js +1 -0
  132. package/dist/index.d.ts +40 -0
  133. package/dist/index.js +21 -0
  134. package/dist/safety/confirmation-store.d.ts +44 -0
  135. package/dist/safety/confirmation-store.js +130 -0
  136. package/dist/safety/guardrail.d.ts +39 -0
  137. package/dist/safety/guardrail.js +99 -0
  138. package/dist/safety/parser/adapter.d.ts +10 -0
  139. package/dist/safety/parser/adapter.js +72 -0
  140. package/dist/safety/parser/ast-utils.d.ts +10 -0
  141. package/dist/safety/parser/ast-utils.js +167 -0
  142. package/dist/safety/parser/features.d.ts +12 -0
  143. package/dist/safety/parser/features.js +113 -0
  144. package/dist/safety/parser/index.d.ts +2 -0
  145. package/dist/safety/parser/index.js +1 -0
  146. package/dist/safety/parser/types.d.ts +76 -0
  147. package/dist/safety/parser/types.js +1 -0
  148. package/dist/safety/redaction.d.ts +34 -0
  149. package/dist/safety/redaction.js +186 -0
  150. package/dist/safety/sql-classifier.d.ts +19 -0
  151. package/dist/safety/sql-classifier.js +43 -0
  152. package/dist/safety/sql-validator.d.ts +19 -0
  153. package/dist/safety/sql-validator.js +143 -0
  154. package/dist/schema/adapters/mysql.d.ts +16 -0
  155. package/dist/schema/adapters/mysql.js +287 -0
  156. package/dist/schema/introspector.d.ts +70 -0
  157. package/dist/schema/introspector.js +40 -0
  158. package/dist/taurus/flashback.d.ts +36 -0
  159. package/dist/taurus/flashback.js +149 -0
  160. package/dist/taurus/recycle-bin.d.ts +14 -0
  161. package/dist/taurus/recycle-bin.js +61 -0
  162. package/dist/utils/formatter.d.ts +70 -0
  163. package/dist/utils/formatter.js +60 -0
  164. package/dist/utils/hash.d.ts +2 -0
  165. package/dist/utils/hash.js +247 -0
  166. package/dist/utils/id.d.ts +2 -0
  167. package/dist/utils/id.js +11 -0
  168. package/dist/utils/logger.d.ts +9 -0
  169. package/dist/utils/logger.js +39 -0
  170. package/package.json +46 -0
@@ -0,0 +1,198 @@
1
+ import { createHash, createHmac } from "node:crypto";
2
+ function readString(value) {
3
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
4
+ }
5
+ function sha256Hex(value) {
6
+ return createHash("sha256").update(value, "utf8").digest("hex");
7
+ }
8
+ function hmacSha256Hex(secret, value) {
9
+ return createHmac("sha256", secret).update(value, "utf8").digest("hex");
10
+ }
11
+ function normalizeHeaderValue(value) {
12
+ return value.trim().replace(/\s+/g, " ");
13
+ }
14
+ function encodeRfc3986(value) {
15
+ return encodeURIComponent(value).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
16
+ }
17
+ function buildCanonicalUri(pathname) {
18
+ const segments = pathname.split("/").map((segment) => encodeRfc3986(segment));
19
+ let normalized = segments.join("/");
20
+ if (!normalized.startsWith("/")) {
21
+ normalized = `/${normalized}`;
22
+ }
23
+ // Huawei Cloud API signing requires the canonical URI to end with a slash.
24
+ return normalized.endsWith("/") ? normalized : `${normalized}/`;
25
+ }
26
+ function buildCanonicalQueryString(url) {
27
+ const pairs = [...url.searchParams.entries()].sort((left, right) => {
28
+ if (left[0] === right[0]) {
29
+ return left[1].localeCompare(right[1]);
30
+ }
31
+ return left[0].localeCompare(right[0]);
32
+ });
33
+ return pairs
34
+ .map(([key, value]) => `${encodeRfc3986(key)}=${encodeRfc3986(value)}`)
35
+ .join("&");
36
+ }
37
+ function formatSdkDate(now) {
38
+ return now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
39
+ }
40
+ function inferRegionFromUrl(url) {
41
+ const labels = url.hostname.split(".");
42
+ return labels.length >= 3 ? labels[1] : undefined;
43
+ }
44
+ function buildHeaderMap(headers, url, sdkDate, securityToken) {
45
+ const merged = new Headers(headers);
46
+ merged.set("host", url.host);
47
+ merged.set("x-sdk-date", sdkDate);
48
+ if (securityToken) {
49
+ merged.set("x-security-token", securityToken);
50
+ }
51
+ const map = new Map();
52
+ for (const [key, value] of merged.entries()) {
53
+ map.set(key.toLowerCase(), normalizeHeaderValue(value));
54
+ }
55
+ return new Map([...map.entries()].sort(([left], [right]) => left.localeCompare(right)));
56
+ }
57
+ function buildAuthorization(method, url, headers, body, accessKeyId, secretAccessKey) {
58
+ const canonicalHeaders = [...headers.entries()]
59
+ .map(([key, value]) => `${key}:${value}\n`)
60
+ .join("");
61
+ const signedHeaders = [...headers.keys()].join(";");
62
+ const canonicalRequest = [
63
+ method.toUpperCase(),
64
+ buildCanonicalUri(url.pathname || "/"),
65
+ buildCanonicalQueryString(url),
66
+ canonicalHeaders,
67
+ signedHeaders,
68
+ sha256Hex(body),
69
+ ].join("\n");
70
+ const stringToSign = `SDK-HMAC-SHA256\n${headers.get("x-sdk-date")}\n${sha256Hex(canonicalRequest)}`;
71
+ const signature = hmacSha256Hex(secretAccessKey, stringToSign);
72
+ return `SDK-HMAC-SHA256 Access=${accessKeyId}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
73
+ }
74
+ function requireAksk(auth) {
75
+ const accessKeyId = readString(auth.accessKeyId);
76
+ const secretAccessKey = readString(auth.secretAccessKey);
77
+ if (!accessKeyId || !secretAccessKey) {
78
+ throw new Error("Huawei Cloud request authentication is not configured. Provide TAURUSDB_CLOUD_AUTH_TOKEN or TAURUSDB_CLOUD_ACCESS_KEY_ID and TAURUSDB_CLOUD_SECRET_ACCESS_KEY.");
79
+ }
80
+ return { accessKeyId, secretAccessKey };
81
+ }
82
+ export function canAuthenticateHuaweiCloudRequests(auth) {
83
+ return Boolean(readString(auth.authToken) ||
84
+ (readString(auth.accessKeyId) && readString(auth.secretAccessKey)));
85
+ }
86
+ export async function fetchHuaweiCloud(options) {
87
+ const fetchImpl = options.fetchImpl ?? fetch;
88
+ const method = options.method ?? "GET";
89
+ const url = new URL(options.url);
90
+ const body = options.body ?? "";
91
+ const authToken = readString(options.auth.authToken);
92
+ const headers = new Headers(options.headers);
93
+ if (authToken) {
94
+ headers.set("x-auth-token", authToken);
95
+ return fetchImpl(url, {
96
+ method,
97
+ headers,
98
+ body: options.body,
99
+ });
100
+ }
101
+ const { accessKeyId, secretAccessKey } = requireAksk(options.auth);
102
+ const sdkDate = formatSdkDate(new Date());
103
+ const headerMap = buildHeaderMap(headers, url, sdkDate, readString(options.auth.securityToken));
104
+ headerMap.set("authorization", buildAuthorization(method, url, headerMap, body, accessKeyId, secretAccessKey));
105
+ return fetchImpl(url, {
106
+ method,
107
+ headers: Object.fromEntries(headerMap.entries()),
108
+ body: options.body,
109
+ });
110
+ }
111
+ function buildIamEndpoint(auth) {
112
+ const explicit = readString(auth.iamEndpoint);
113
+ if (explicit) {
114
+ return explicit.replace(/\/+$/g, "");
115
+ }
116
+ const region = readString(auth.region);
117
+ if (!region) {
118
+ return undefined;
119
+ }
120
+ const domainSuffix = readString(auth.domainSuffix) ?? "myhuaweicloud.com";
121
+ return `https://iam.${region}.${domainSuffix}`;
122
+ }
123
+ function parseProjectItems(payload) {
124
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
125
+ return [];
126
+ }
127
+ const record = payload;
128
+ const projects = record.projects ?? record.project_info ?? record.items;
129
+ if (!Array.isArray(projects)) {
130
+ return [];
131
+ }
132
+ return projects.filter((item) => item !== null && typeof item === "object" && !Array.isArray(item));
133
+ }
134
+ export async function resolveHuaweiCloudProjectId(auth, fetchImpl) {
135
+ const explicitProjectId = readString(auth.projectId);
136
+ if (explicitProjectId) {
137
+ return explicitProjectId;
138
+ }
139
+ const iamEndpoint = buildIamEndpoint(auth);
140
+ const region = readString(auth.region);
141
+ if (!iamEndpoint || !region) {
142
+ return undefined;
143
+ }
144
+ const response = await fetchHuaweiCloud({
145
+ url: `${iamEndpoint}/v3/auth/projects`,
146
+ headers: {
147
+ "content-type": "application/json",
148
+ },
149
+ auth,
150
+ fetchImpl,
151
+ });
152
+ const payload = (await response.json().catch(() => ({})));
153
+ if (!response.ok) {
154
+ const record = payload && typeof payload === "object" && !Array.isArray(payload)
155
+ ? payload
156
+ : {};
157
+ const code = readString(record.error_code) ?? readString(record.code);
158
+ const message = readString(record.error_msg) ?? readString(record.message);
159
+ throw new Error(`Huawei Cloud project lookup failed with status ${response.status}${code ? ` (${code})` : ""}${message ? `: ${message}` : ""}.`);
160
+ }
161
+ const projects = parseProjectItems(payload);
162
+ const exactMatch = projects.find((project) => readString(project.name) === region);
163
+ if (exactMatch) {
164
+ return readString(exactMatch.id);
165
+ }
166
+ if (projects.length === 1) {
167
+ return readString(projects[0].id);
168
+ }
169
+ throw new Error(`Unable to resolve project id for region ${region}. Set TAURUSDB_CLOUD_PROJECT_ID explicitly or restrict the account to a single visible project in that region.`);
170
+ }
171
+ export function getHuaweiCloudAuthFromConfig(config) {
172
+ return {
173
+ region: config.cloud?.region,
174
+ projectId: config.cloud?.projectId,
175
+ authToken: config.cloud?.authToken,
176
+ accessKeyId: config.cloud?.accessKeyId,
177
+ secretAccessKey: config.cloud?.secretAccessKey,
178
+ securityToken: config.cloud?.securityToken,
179
+ domainSuffix: config.cloud?.domainSuffix,
180
+ iamEndpoint: config.cloud?.iamEndpoint,
181
+ language: config.cloud?.language,
182
+ };
183
+ }
184
+ export function hasHuaweiCloudCredentialAuth(config) {
185
+ return canAuthenticateHuaweiCloudRequests(getHuaweiCloudAuthFromConfig(config));
186
+ }
187
+ export function inferHuaweiCloudRegionFromEndpoint(endpoint) {
188
+ const normalized = readString(endpoint);
189
+ if (!normalized) {
190
+ return undefined;
191
+ }
192
+ try {
193
+ return inferRegionFromUrl(new URL(normalized));
194
+ }
195
+ catch {
196
+ return undefined;
197
+ }
198
+ }
@@ -0,0 +1,46 @@
1
+ import type { Config } from "../config/index.js";
2
+ import { type HuaweiCloudAuthOptions } from "./auth.js";
3
+ export interface ListCloudTaurusInstancesInput {
4
+ name?: string;
5
+ id?: string;
6
+ ip?: string;
7
+ offset?: number;
8
+ limit?: number;
9
+ }
10
+ export interface CloudTaurusInstanceSummary {
11
+ id: string;
12
+ name: string;
13
+ status?: string;
14
+ mode?: string;
15
+ region?: string;
16
+ datastoreVersion?: string;
17
+ vpcId?: string;
18
+ subnetId?: string;
19
+ privateIps: string[];
20
+ publicIps: string[];
21
+ hostnames: string[];
22
+ port?: string;
23
+ nodeIds: string[];
24
+ primaryNodeId?: string;
25
+ created?: string;
26
+ updated?: string;
27
+ }
28
+ type CloudInstanceClientOptions = {
29
+ endpoint: string;
30
+ auth: HuaweiCloudAuthOptions;
31
+ language: "en-us" | "zh-cn";
32
+ fetchImpl?: typeof fetch;
33
+ };
34
+ export declare class CloudTaurusInstanceClient {
35
+ private readonly endpoint;
36
+ private readonly auth;
37
+ private readonly language;
38
+ private readonly fetchImpl;
39
+ private resolvedProjectId?;
40
+ constructor(options: CloudInstanceClientOptions);
41
+ getProjectId(): Promise<string>;
42
+ list(input?: ListCloudTaurusInstancesInput): Promise<CloudTaurusInstanceSummary[]>;
43
+ resolveByHostPort(host: string, port?: string | number): Promise<CloudTaurusInstanceSummary>;
44
+ }
45
+ export declare function createCloudTaurusInstanceClient(config: Config): CloudTaurusInstanceClient | undefined;
46
+ export {};
@@ -0,0 +1,224 @@
1
+ import { fetchHuaweiCloud, getHuaweiCloudAuthFromConfig, resolveHuaweiCloudProjectId, } from "./auth.js";
2
+ function readString(value) {
3
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
4
+ }
5
+ function readStringList(value) {
6
+ if (!Array.isArray(value)) {
7
+ return [];
8
+ }
9
+ return value
10
+ .map((item) => readString(item))
11
+ .filter((item) => item !== undefined);
12
+ }
13
+ function readNestedAddressList(value, key) {
14
+ if (!Array.isArray(value)) {
15
+ return [];
16
+ }
17
+ return value
18
+ .map((item) => {
19
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
20
+ return undefined;
21
+ }
22
+ return readString(item[key]);
23
+ })
24
+ .filter((item) => item !== undefined);
25
+ }
26
+ function clampInteger(value, fallback, min, max) {
27
+ if (value === undefined || !Number.isInteger(value)) {
28
+ return fallback;
29
+ }
30
+ return Math.min(Math.max(value, min), max);
31
+ }
32
+ function buildQueryString(input) {
33
+ const params = new URLSearchParams();
34
+ const offset = clampInteger(input.offset, 0, 0, 10_000);
35
+ const limit = clampInteger(input.limit, 50, 1, 100);
36
+ params.set("offset", String(offset));
37
+ params.set("limit", String(limit));
38
+ const name = readString(input.name);
39
+ if (name) {
40
+ params.set("name", name);
41
+ }
42
+ const id = readString(input.id);
43
+ if (id) {
44
+ params.set("id", id);
45
+ }
46
+ const ip = readString(input.ip);
47
+ if (ip) {
48
+ params.set("ip", ip);
49
+ }
50
+ return params.toString();
51
+ }
52
+ function parseInstanceSummary(item) {
53
+ const id = readString(item.id);
54
+ const name = readString(item.name);
55
+ if (!id || !name) {
56
+ return undefined;
57
+ }
58
+ const nodes = Array.isArray(item.nodes)
59
+ ? item.nodes.filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry))
60
+ : [];
61
+ const nodeIds = nodes
62
+ .map((entry) => readString(entry.id) ?? readString(entry.node_id))
63
+ .filter((entry) => entry !== undefined);
64
+ const primaryNode = nodes.find((entry) => {
65
+ const role = readString(entry.role) ??
66
+ readString(entry.type) ??
67
+ readString(entry.node_type);
68
+ return Boolean(role && /(master|primary|writer|readwrite)/i.test(role));
69
+ }) ?? (nodes.length === 1 ? nodes[0] : undefined);
70
+ const port = readString(item.port) ?? readString(item.db_port);
71
+ return {
72
+ id,
73
+ name,
74
+ status: readString(item.status),
75
+ mode: readString(item.mode),
76
+ region: readString(item.region),
77
+ datastoreVersion: readString(item.datastore?.version) ??
78
+ readString(item.db_type),
79
+ vpcId: readString(item.vpc_security_group?.vpc_id) ??
80
+ readString(item.vpc_id),
81
+ subnetId: readString(item.vpc_security_group?.subnet_id) ??
82
+ readString(item.subnet_id),
83
+ privateIps: [
84
+ ...readStringList(item.private_ips),
85
+ ...readStringList(item.proxy_ips),
86
+ ...readStringList(item.readonly_private_ips),
87
+ ...readNestedAddressList(item.nodes, "private_ip"),
88
+ ].filter((value, index, allItems) => allItems.indexOf(value) === index),
89
+ publicIps: readStringList(item.public_ips),
90
+ hostnames: [
91
+ readString(item.private_dns),
92
+ readString(item.public_dns),
93
+ readString(item.db_domain),
94
+ readString(item.readonly_domain),
95
+ readString(item.alias),
96
+ ].filter((value, index, allItems) => value !== undefined && allItems.indexOf(value) === index),
97
+ port,
98
+ nodeIds,
99
+ primaryNodeId: readString(primaryNode?.id) ??
100
+ readString(primaryNode?.node_id) ??
101
+ (nodeIds.length === 1 ? nodeIds[0] : undefined),
102
+ created: readString(item.created),
103
+ updated: readString(item.updated),
104
+ };
105
+ }
106
+ function normalizeHost(host) {
107
+ return host.trim().toLowerCase();
108
+ }
109
+ function normalizePort(port) {
110
+ if (typeof port === "number" && Number.isFinite(port)) {
111
+ return String(port);
112
+ }
113
+ return readString(typeof port === "string" ? port : undefined);
114
+ }
115
+ async function parseJsonObject(response) {
116
+ const payload = (await response.json());
117
+ return payload && typeof payload === "object" && !Array.isArray(payload)
118
+ ? payload
119
+ : {};
120
+ }
121
+ export class CloudTaurusInstanceClient {
122
+ endpoint;
123
+ auth;
124
+ language;
125
+ fetchImpl;
126
+ resolvedProjectId;
127
+ constructor(options) {
128
+ this.endpoint = options.endpoint.replace(/\/+$/g, "");
129
+ this.auth = options.auth;
130
+ this.language = options.language;
131
+ this.fetchImpl = options.fetchImpl ?? fetch;
132
+ this.resolvedProjectId = readString(options.auth.projectId);
133
+ }
134
+ async getProjectId() {
135
+ if (!this.resolvedProjectId) {
136
+ this.resolvedProjectId = await resolveHuaweiCloudProjectId(this.auth, this.fetchImpl);
137
+ }
138
+ if (!this.resolvedProjectId) {
139
+ throw new Error("Cloud instance discovery could not resolve a Huawei Cloud project id. Provide TAURUSDB_CLOUD_PROJECT_ID or configure region plus AK/SK credentials.");
140
+ }
141
+ return this.resolvedProjectId;
142
+ }
143
+ async list(input = {}) {
144
+ const query = buildQueryString(input);
145
+ const projectId = await this.getProjectId();
146
+ const response = await fetchHuaweiCloud({
147
+ url: `${this.endpoint}/v3/${projectId}/instances?${query}`,
148
+ headers: {
149
+ "content-type": "application/json",
150
+ "x-language": this.language,
151
+ },
152
+ auth: this.auth,
153
+ fetchImpl: this.fetchImpl,
154
+ });
155
+ if (!response.ok) {
156
+ const payload = (await parseJsonObject(response).catch(() => ({})));
157
+ const code = readString(payload.error_code) ?? readString(payload.code);
158
+ const message = readString(payload.error_msg) ?? readString(payload.message);
159
+ throw new Error(`Cloud instance list request failed with status ${response.status}${code ? ` (${code})` : ""}${message ? `: ${message}` : ""}.`);
160
+ }
161
+ const payload = await parseJsonObject(response);
162
+ const instances = Array.isArray(payload.instances)
163
+ ? payload.instances
164
+ : Array.isArray(payload.instance_info)
165
+ ? payload.instance_info
166
+ : [];
167
+ return instances
168
+ .map((item) => item && typeof item === "object" && !Array.isArray(item)
169
+ ? parseInstanceSummary(item)
170
+ : undefined)
171
+ .filter((item) => item !== undefined);
172
+ }
173
+ async resolveByHostPort(host, port) {
174
+ const normalizedHost = normalizeHost(host);
175
+ const normalizedPort = normalizePort(port);
176
+ const instances = await this.list({
177
+ ip: normalizedHost,
178
+ limit: 100,
179
+ });
180
+ const matches = instances.filter((item) => {
181
+ const hosts = [
182
+ ...item.privateIps,
183
+ ...item.publicIps,
184
+ ...item.hostnames,
185
+ ].map(normalizeHost);
186
+ if (!hosts.includes(normalizedHost)) {
187
+ return false;
188
+ }
189
+ if (!normalizedPort || !item.port) {
190
+ return true;
191
+ }
192
+ return item.port === normalizedPort;
193
+ });
194
+ if (matches.length === 1) {
195
+ return matches[0];
196
+ }
197
+ if (matches.length === 0) {
198
+ throw new Error(`No cloud instance matched datasource address ${host}${normalizedPort ? `:${normalizedPort}` : ""}. Use list_cloud_taurus_instances and provide an explicit instance id.`);
199
+ }
200
+ throw new Error(`Multiple cloud instances matched datasource address ${host}${normalizedPort ? `:${normalizedPort}` : ""}. Use list_cloud_taurus_instances and select an explicit instance id.`);
201
+ }
202
+ }
203
+ export function createCloudTaurusInstanceClient(config) {
204
+ const endpoint = config.cloud?.apiEndpoint ??
205
+ config.slowSqlSource?.taurusApi?.endpoint ??
206
+ config.metricsSource?.ces?.endpoint;
207
+ const language = config.cloud?.language ?? "zh-cn";
208
+ if (!endpoint) {
209
+ return undefined;
210
+ }
211
+ return new CloudTaurusInstanceClient({
212
+ endpoint,
213
+ auth: {
214
+ ...getHuaweiCloudAuthFromConfig(config),
215
+ projectId: config.cloud?.projectId ??
216
+ config.slowSqlSource?.taurusApi?.projectId ??
217
+ config.metricsSource?.ces?.projectId,
218
+ authToken: config.cloud?.authToken ??
219
+ config.slowSqlSource?.taurusApi?.authToken ??
220
+ config.metricsSource?.ces?.authToken,
221
+ },
222
+ language,
223
+ });
224
+ }
@@ -0,0 +1 @@
1
+ export declare function buildRawConfigFromEnv(env: NodeJS.ProcessEnv): Record<string, unknown>;
@@ -0,0 +1,194 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ const HUAWEI_CLOUD_DEFAULT_DOMAIN_SUFFIX = "myhuaweicloud.com";
4
+ const REGION_PATTERN = /^[a-z]{2}(?:-[a-z0-9]+)+-\d+$/i;
5
+ function readString(value) {
6
+ if (typeof value !== "string") {
7
+ return undefined;
8
+ }
9
+ const trimmed = value.trim();
10
+ return trimmed.length > 0 ? trimmed : undefined;
11
+ }
12
+ function parseBoolean(value, name) {
13
+ const normalized = readString(value)?.toLowerCase();
14
+ if (normalized === undefined) {
15
+ return undefined;
16
+ }
17
+ if (normalized === "true" ||
18
+ normalized === "1" ||
19
+ normalized === "yes" ||
20
+ normalized === "on") {
21
+ return true;
22
+ }
23
+ if (normalized === "false" ||
24
+ normalized === "0" ||
25
+ normalized === "no" ||
26
+ normalized === "off") {
27
+ return false;
28
+ }
29
+ throw new Error(`Invalid boolean for ${name}: "${value}". Expected one of true/false/1/0/yes/no/on/off.`);
30
+ }
31
+ function parseInteger(value, name) {
32
+ const normalized = readString(value);
33
+ if (normalized === undefined) {
34
+ return undefined;
35
+ }
36
+ if (!/^-?\d+$/.test(normalized)) {
37
+ throw new Error(`Invalid integer for ${name}: "${value}".`);
38
+ }
39
+ const parsed = Number.parseInt(normalized, 10);
40
+ if (!Number.isFinite(parsed) || Number.isNaN(parsed)) {
41
+ throw new Error(`Invalid integer for ${name}: "${value}".`);
42
+ }
43
+ return parsed;
44
+ }
45
+ function pickFirstDefined(...values) {
46
+ for (const value of values) {
47
+ if (value !== undefined) {
48
+ return value;
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+ function hasSqlTemplateInputs(env) {
54
+ return Boolean(readString(env.TAURUSDB_SQL_DSN) ||
55
+ readString(env.TAURUSDB_SQL_HOST) ||
56
+ readString(env.TAURUSDB_SQL_DATABASE) ||
57
+ readString(env.TAURUSDB_SQL_USER) ||
58
+ readString(env.TAURUSDB_SQL_PASSWORD));
59
+ }
60
+ function inferRegionFromValue(value) {
61
+ const normalized = readString(value);
62
+ if (!normalized) {
63
+ return undefined;
64
+ }
65
+ const directMatch = normalized.match(REGION_PATTERN);
66
+ if (directMatch) {
67
+ return directMatch[0].toLowerCase();
68
+ }
69
+ try {
70
+ const hostname = normalized.includes("://")
71
+ ? new URL(normalized).hostname
72
+ : normalized;
73
+ const labels = hostname.split(".");
74
+ for (const label of labels) {
75
+ if (REGION_PATTERN.test(label)) {
76
+ return label.toLowerCase();
77
+ }
78
+ }
79
+ }
80
+ catch {
81
+ return undefined;
82
+ }
83
+ return undefined;
84
+ }
85
+ function buildHuaweiCloudEndpoint(service, region, domainSuffix) {
86
+ if (!region) {
87
+ return undefined;
88
+ }
89
+ return `https://${service}.${region}.${domainSuffix ?? HUAWEI_CLOUD_DEFAULT_DOMAIN_SUFFIX}`;
90
+ }
91
+ function expandTildePath(inputPath) {
92
+ if (!inputPath) {
93
+ return undefined;
94
+ }
95
+ if (inputPath === "~") {
96
+ return os.homedir();
97
+ }
98
+ if (inputPath.startsWith("~/")) {
99
+ return path.join(os.homedir(), inputPath.slice(2));
100
+ }
101
+ return inputPath;
102
+ }
103
+ export function buildRawConfigFromEnv(env) {
104
+ const cloudDasEnabled = parseBoolean(env.TAURUSDB_CLOUD_ENABLE_DAS, "TAURUSDB_CLOUD_ENABLE_DAS");
105
+ const cloudCesEnabled = parseBoolean(env.TAURUSDB_CLOUD_ENABLE_CES, "TAURUSDB_CLOUD_ENABLE_CES");
106
+ const cloudTaurusApiEnabled = parseBoolean(env.TAURUSDB_CLOUD_ENABLE_TAURUS_API, "TAURUSDB_CLOUD_ENABLE_TAURUS_API");
107
+ const cloudDomainSuffix = readString(env.TAURUSDB_CLOUD_DOMAIN_SUFFIX) ??
108
+ HUAWEI_CLOUD_DEFAULT_DOMAIN_SUFFIX;
109
+ const cloudRegion = pickFirstDefined(readString(env.TAURUSDB_CLOUD_REGION), inferRegionFromValue(env.TAURUSDB_SQL_HOST), inferRegionFromValue(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_ENDPOINT), inferRegionFromValue(env.TAURUSDB_METRICS_SOURCE_CES_ENDPOINT), inferRegionFromValue(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_ENDPOINT));
110
+ const cloudProjectId = readString(env.TAURUSDB_CLOUD_PROJECT_ID);
111
+ const cloudInstanceId = readString(env.TAURUSDB_CLOUD_INSTANCE_ID);
112
+ const cloudNodeId = readString(env.TAURUSDB_CLOUD_NODE_ID);
113
+ const cloudAuthToken = readString(env.TAURUSDB_CLOUD_AUTH_TOKEN);
114
+ const cloudAccessKeyId = pickFirstDefined(readString(env.TAURUSDB_CLOUD_ACCESS_KEY_ID), readString(env.TAURUSDB_CLOUD_AK));
115
+ const cloudSecretAccessKey = pickFirstDefined(readString(env.TAURUSDB_CLOUD_SECRET_ACCESS_KEY), readString(env.TAURUSDB_CLOUD_SK));
116
+ const cloudSecurityToken = pickFirstDefined(readString(env.TAURUSDB_CLOUD_SECURITY_TOKEN), readString(env.TAURUSDB_CLOUD_SESSION_TOKEN));
117
+ const inferredDatasourceName = hasSqlTemplateInputs(env)
118
+ ? readString(env.TAURUSDB_SQL_DATASOURCE) ?? "taurus_mcp"
119
+ : undefined;
120
+ return {
121
+ defaultDatasource: readString(env.TAURUSDB_DEFAULT_DATASOURCE) ?? inferredDatasourceName,
122
+ profilesPath: expandTildePath(readString(env.TAURUSDB_SQL_PROFILES)),
123
+ enableMutations: parseBoolean(env.TAURUSDB_MCP_ENABLE_MUTATIONS, "TAURUSDB_MCP_ENABLE_MUTATIONS"),
124
+ cloud: {
125
+ provider: "huaweicloud",
126
+ region: cloudRegion,
127
+ projectId: cloudProjectId,
128
+ instanceId: cloudInstanceId,
129
+ nodeId: cloudNodeId,
130
+ authToken: cloudAuthToken,
131
+ accessKeyId: cloudAccessKeyId,
132
+ secretAccessKey: cloudSecretAccessKey,
133
+ securityToken: cloudSecurityToken,
134
+ apiEndpoint: buildHuaweiCloudEndpoint("gaussdb", cloudRegion, cloudDomainSuffix),
135
+ iamEndpoint: buildHuaweiCloudEndpoint("iam", cloudRegion, cloudDomainSuffix),
136
+ domainSuffix: cloudDomainSuffix,
137
+ language: readString(env.TAURUSDB_CLOUD_LANGUAGE) ??
138
+ readString(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_LANGUAGE),
139
+ },
140
+ limits: {
141
+ maxRows: parseInteger(env.TAURUSDB_MCP_MAX_ROWS, "TAURUSDB_MCP_MAX_ROWS"),
142
+ maxColumns: parseInteger(env.TAURUSDB_MCP_MAX_COLUMNS, "TAURUSDB_MCP_MAX_COLUMNS"),
143
+ maxStatementMs: parseInteger(env.TAURUSDB_MCP_MAX_STATEMENT_MS, "TAURUSDB_MCP_MAX_STATEMENT_MS"),
144
+ maxFieldChars: parseInteger(env.TAURUSDB_MCP_MAX_FIELD_CHARS, "TAURUSDB_MCP_MAX_FIELD_CHARS"),
145
+ },
146
+ audit: {
147
+ logPath: expandTildePath(readString(env.TAURUSDB_MCP_AUDIT_LOG_PATH)),
148
+ includeRawSql: parseBoolean(env.TAURUSDB_MCP_AUDIT_INCLUDE_RAW_SQL, "TAURUSDB_MCP_AUDIT_INCLUDE_RAW_SQL"),
149
+ },
150
+ slowSqlSource: {
151
+ taurusApi: {
152
+ enabled: pickFirstDefined(parseBoolean(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_ENABLED, "TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_ENABLED"), cloudTaurusApiEnabled),
153
+ endpoint: pickFirstDefined(readString(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_ENDPOINT), buildHuaweiCloudEndpoint("gaussdb", cloudRegion, cloudDomainSuffix)),
154
+ projectId: pickFirstDefined(readString(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_PROJECT_ID), cloudProjectId),
155
+ instanceId: pickFirstDefined(readString(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_INSTANCE_ID), cloudInstanceId),
156
+ nodeId: pickFirstDefined(readString(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_NODE_ID), cloudNodeId),
157
+ authToken: pickFirstDefined(readString(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_AUTH_TOKEN), cloudAuthToken),
158
+ language: readString(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_LANGUAGE),
159
+ requestTimeoutMs: parseInteger(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_TIMEOUT_MS, "TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_TIMEOUT_MS"),
160
+ defaultLookbackMinutes: parseInteger(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_DEFAULT_LOOKBACK_MINUTES, "TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_DEFAULT_LOOKBACK_MINUTES"),
161
+ maxRecords: parseInteger(env.TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_MAX_RECORDS, "TAURUSDB_SLOW_SQL_SOURCE_TAURUS_API_MAX_RECORDS"),
162
+ },
163
+ das: {
164
+ enabled: pickFirstDefined(parseBoolean(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_ENABLED, "TAURUSDB_SLOW_SQL_SOURCE_DAS_ENABLED"), cloudDasEnabled, true),
165
+ endpoint: pickFirstDefined(readString(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_ENDPOINT), buildHuaweiCloudEndpoint("das", cloudRegion, cloudDomainSuffix)),
166
+ projectId: pickFirstDefined(readString(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_PROJECT_ID), cloudProjectId),
167
+ instanceId: pickFirstDefined(readString(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_INSTANCE_ID), cloudInstanceId),
168
+ authToken: pickFirstDefined(readString(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_AUTH_TOKEN), cloudAuthToken),
169
+ datastoreType: readString(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_DATASTORE_TYPE),
170
+ requestTimeoutMs: parseInteger(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_TIMEOUT_MS, "TAURUSDB_SLOW_SQL_SOURCE_DAS_TIMEOUT_MS"),
171
+ defaultLookbackMinutes: parseInteger(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_DEFAULT_LOOKBACK_MINUTES, "TAURUSDB_SLOW_SQL_SOURCE_DAS_DEFAULT_LOOKBACK_MINUTES"),
172
+ maxRecords: parseInteger(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_MAX_RECORDS, "TAURUSDB_SLOW_SQL_SOURCE_DAS_MAX_RECORDS"),
173
+ maxPages: parseInteger(env.TAURUSDB_SLOW_SQL_SOURCE_DAS_MAX_PAGES, "TAURUSDB_SLOW_SQL_SOURCE_DAS_MAX_PAGES"),
174
+ },
175
+ },
176
+ metricsSource: {
177
+ ces: {
178
+ enabled: pickFirstDefined(parseBoolean(env.TAURUSDB_METRICS_SOURCE_CES_ENABLED, "TAURUSDB_METRICS_SOURCE_CES_ENABLED"), cloudCesEnabled, true),
179
+ endpoint: pickFirstDefined(readString(env.TAURUSDB_METRICS_SOURCE_CES_ENDPOINT), buildHuaweiCloudEndpoint("ces", cloudRegion, cloudDomainSuffix)),
180
+ projectId: pickFirstDefined(readString(env.TAURUSDB_METRICS_SOURCE_CES_PROJECT_ID), cloudProjectId),
181
+ instanceId: pickFirstDefined(readString(env.TAURUSDB_METRICS_SOURCE_CES_INSTANCE_ID), cloudInstanceId),
182
+ nodeId: pickFirstDefined(readString(env.TAURUSDB_METRICS_SOURCE_CES_NODE_ID), cloudNodeId),
183
+ authToken: pickFirstDefined(readString(env.TAURUSDB_METRICS_SOURCE_CES_AUTH_TOKEN), cloudAuthToken),
184
+ namespace: readString(env.TAURUSDB_METRICS_SOURCE_CES_NAMESPACE),
185
+ instanceDimension: readString(env.TAURUSDB_METRICS_SOURCE_CES_INSTANCE_DIMENSION),
186
+ nodeDimension: readString(env.TAURUSDB_METRICS_SOURCE_CES_NODE_DIMENSION),
187
+ period: readString(env.TAURUSDB_METRICS_SOURCE_CES_PERIOD),
188
+ filter: readString(env.TAURUSDB_METRICS_SOURCE_CES_FILTER),
189
+ requestTimeoutMs: parseInteger(env.TAURUSDB_METRICS_SOURCE_CES_TIMEOUT_MS, "TAURUSDB_METRICS_SOURCE_CES_TIMEOUT_MS"),
190
+ defaultLookbackMinutes: parseInteger(env.TAURUSDB_METRICS_SOURCE_CES_DEFAULT_LOOKBACK_MINUTES, "TAURUSDB_METRICS_SOURCE_CES_DEFAULT_LOOKBACK_MINUTES"),
191
+ },
192
+ },
193
+ };
194
+ }
@@ -0,0 +1,6 @@
1
+ import { type Config } from "./schema.js";
2
+ export { redactConfigForLog } from "./redaction.js";
3
+ export type { Config } from "./schema.js";
4
+ export declare function createConfigFromEnv(env?: NodeJS.ProcessEnv): Config;
5
+ export declare function getConfig(): Config;
6
+ export declare function resetConfigForTests(): void;