@rmwxxwmr/mcp-database-service 0.1.2
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/LICENSE +21 -0
- package/README.md +236 -0
- package/README.zh-CN.md +236 -0
- package/config/databases.example.json +48 -0
- package/dist/config/configSummary.d.ts +4 -0
- package/dist/config/configSummary.js +117 -0
- package/dist/config/configSummary.js.map +1 -0
- package/dist/config/configTypes.d.ts +81 -0
- package/dist/config/configTypes.js +2 -0
- package/dist/config/configTypes.js.map +1 -0
- package/dist/config/configValidation.d.ts +6 -0
- package/dist/config/configValidation.js +142 -0
- package/dist/config/configValidation.js.map +1 -0
- package/dist/config/loadConfig.d.ts +8 -0
- package/dist/config/loadConfig.js +85 -0
- package/dist/config/loadConfig.js.map +1 -0
- package/dist/core/errors.d.ts +15 -0
- package/dist/core/errors.js +28 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/logger.d.ts +7 -0
- package/dist/core/logger.js +68 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/resultTypes.d.ts +49 -0
- package/dist/core/resultTypes.js +2 -0
- package/dist/core/resultTypes.js.map +1 -0
- package/dist/db/clientFactory.d.ts +9 -0
- package/dist/db/clientFactory.js +27 -0
- package/dist/db/clientFactory.js.map +1 -0
- package/dist/db/readonlyGuard.d.ts +14 -0
- package/dist/db/readonlyGuard.js +253 -0
- package/dist/db/readonlyGuard.js.map +1 -0
- package/dist/db/redis/redisClient.d.ts +16 -0
- package/dist/db/redis/redisClient.js +106 -0
- package/dist/db/redis/redisClient.js.map +1 -0
- package/dist/db/sql/baseSqlAdapter.d.ts +52 -0
- package/dist/db/sql/baseSqlAdapter.js +310 -0
- package/dist/db/sql/baseSqlAdapter.js.map +1 -0
- package/dist/db/sql/mysqlClient.d.ts +33 -0
- package/dist/db/sql/mysqlClient.js +150 -0
- package/dist/db/sql/mysqlClient.js.map +1 -0
- package/dist/db/sql/openGaussClient.d.ts +9 -0
- package/dist/db/sql/openGaussClient.js +11 -0
- package/dist/db/sql/openGaussClient.js.map +1 -0
- package/dist/db/sql/oracleClient.d.ts +33 -0
- package/dist/db/sql/oracleClient.js +203 -0
- package/dist/db/sql/oracleClient.js.map +1 -0
- package/dist/db/sql/postgresClient.d.ts +33 -0
- package/dist/db/sql/postgresClient.js +160 -0
- package/dist/db/sql/postgresClient.js.map +1 -0
- package/dist/db/types.d.ts +24 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -0
- package/dist/server/createServer.d.ts +46 -0
- package/dist/server/createServer.js +441 -0
- package/dist/server/createServer.js.map +1 -0
- package/dist/server/toolRegistry.d.ts +36 -0
- package/dist/server/toolRegistry.js +927 -0
- package/dist/server/toolRegistry.js.map +1 -0
- package/dist/utils/normalize.d.ts +6 -0
- package/dist/utils/normalize.js +29 -0
- package/dist/utils/normalize.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type DatabaseType = "mysql" | "oracle" | "postgresql" | "redis" | "opengauss";
|
|
2
|
+
export interface BaseDatabaseConfig {
|
|
3
|
+
key: string;
|
|
4
|
+
type: DatabaseType;
|
|
5
|
+
readonly: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface MysqlDatabaseConfig extends BaseDatabaseConfig {
|
|
8
|
+
type: "mysql";
|
|
9
|
+
connection: {
|
|
10
|
+
host: string;
|
|
11
|
+
port?: number;
|
|
12
|
+
databaseName: string;
|
|
13
|
+
user: string;
|
|
14
|
+
password: string;
|
|
15
|
+
connectTimeoutMs?: number;
|
|
16
|
+
ssl?: boolean | Record<string, unknown>;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface OracleDatabaseConfig extends BaseDatabaseConfig {
|
|
20
|
+
type: "oracle";
|
|
21
|
+
connection: {
|
|
22
|
+
host: string;
|
|
23
|
+
port?: number;
|
|
24
|
+
serviceName?: string;
|
|
25
|
+
sid?: string;
|
|
26
|
+
user: string;
|
|
27
|
+
password: string;
|
|
28
|
+
connectTimeoutMs?: number;
|
|
29
|
+
clientMode?: "thin" | "thick";
|
|
30
|
+
clientLibDir?: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface PostgresDatabaseConfig extends BaseDatabaseConfig {
|
|
34
|
+
type: "postgresql" | "opengauss";
|
|
35
|
+
connection: {
|
|
36
|
+
host: string;
|
|
37
|
+
port?: number;
|
|
38
|
+
databaseName: string;
|
|
39
|
+
user: string;
|
|
40
|
+
password: string;
|
|
41
|
+
connectTimeoutMs?: number;
|
|
42
|
+
ssl?: boolean | Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export interface RedisDatabaseConfig extends BaseDatabaseConfig {
|
|
46
|
+
type: "redis";
|
|
47
|
+
connection: {
|
|
48
|
+
url?: string;
|
|
49
|
+
host?: string;
|
|
50
|
+
port?: number;
|
|
51
|
+
databaseName?: number;
|
|
52
|
+
username?: string;
|
|
53
|
+
password?: string;
|
|
54
|
+
connectTimeoutMs?: number;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export type DatabaseConfig = MysqlDatabaseConfig | OracleDatabaseConfig | PostgresDatabaseConfig | RedisDatabaseConfig;
|
|
58
|
+
export interface LoggingConfig {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
directory?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface QueryConfig {
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface RootConfig {
|
|
66
|
+
databases: DatabaseConfig[];
|
|
67
|
+
logging: LoggingConfig;
|
|
68
|
+
query: QueryConfig;
|
|
69
|
+
}
|
|
70
|
+
export interface LoadedConfig {
|
|
71
|
+
configPath: string;
|
|
72
|
+
loadedAt: string;
|
|
73
|
+
databases: DatabaseConfig[];
|
|
74
|
+
databaseMap: Map<string, DatabaseConfig>;
|
|
75
|
+
logging: LoggingConfig & {
|
|
76
|
+
directory: string;
|
|
77
|
+
};
|
|
78
|
+
query: {
|
|
79
|
+
timeoutMs: number | null;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configTypes.js","sourceRoot":"","sources":["../../src/config/configTypes.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ApplicationError } from "../core/errors.js";
|
|
3
|
+
const readonlySchema = z.boolean();
|
|
4
|
+
const keySchema = z.string().min(1);
|
|
5
|
+
const mysqlSchema = z.object({
|
|
6
|
+
key: keySchema,
|
|
7
|
+
type: z.literal("mysql"),
|
|
8
|
+
readonly: readonlySchema,
|
|
9
|
+
connection: z.object({
|
|
10
|
+
host: z.string().min(1),
|
|
11
|
+
port: z.number().int().positive().optional(),
|
|
12
|
+
databaseName: z.string().min(1),
|
|
13
|
+
user: z.string().min(1),
|
|
14
|
+
password: z.string(),
|
|
15
|
+
connectTimeoutMs: z.number().int().positive().optional(),
|
|
16
|
+
ssl: z.union([z.boolean(), z.record(z.unknown())]).optional()
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
const oracleSchema = z.object({
|
|
20
|
+
key: keySchema,
|
|
21
|
+
type: z.literal("oracle"),
|
|
22
|
+
readonly: readonlySchema,
|
|
23
|
+
connection: z
|
|
24
|
+
.object({
|
|
25
|
+
host: z.string().min(1),
|
|
26
|
+
port: z.number().int().positive().optional(),
|
|
27
|
+
serviceName: z.string().min(1).optional(),
|
|
28
|
+
sid: z.string().min(1).optional(),
|
|
29
|
+
user: z.string().min(1),
|
|
30
|
+
password: z.string(),
|
|
31
|
+
connectTimeoutMs: z.number().int().positive().optional(),
|
|
32
|
+
clientMode: z.union([z.literal("thin"), z.literal("thick")]).optional(),
|
|
33
|
+
clientLibDir: z.string().min(1).optional()
|
|
34
|
+
})
|
|
35
|
+
.superRefine((value, context) => {
|
|
36
|
+
if (!value.serviceName && !value.sid) {
|
|
37
|
+
context.addIssue({
|
|
38
|
+
code: z.ZodIssueCode.custom,
|
|
39
|
+
message: "Oracle connection requires either serviceName or sid"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (value.clientMode === "thick" && !value.clientLibDir) {
|
|
43
|
+
context.addIssue({
|
|
44
|
+
code: z.ZodIssueCode.custom,
|
|
45
|
+
message: "Oracle thick mode requires clientLibDir"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
const postgresConnectionSchema = z.object({
|
|
51
|
+
host: z.string().min(1),
|
|
52
|
+
port: z.number().int().positive().optional(),
|
|
53
|
+
databaseName: z.string().min(1),
|
|
54
|
+
user: z.string().min(1),
|
|
55
|
+
password: z.string(),
|
|
56
|
+
connectTimeoutMs: z.number().int().positive().optional(),
|
|
57
|
+
ssl: z.union([z.boolean(), z.record(z.unknown())]).optional()
|
|
58
|
+
});
|
|
59
|
+
const postgresSchema = z.object({
|
|
60
|
+
key: keySchema,
|
|
61
|
+
type: z.literal("postgresql"),
|
|
62
|
+
readonly: readonlySchema,
|
|
63
|
+
connection: postgresConnectionSchema
|
|
64
|
+
});
|
|
65
|
+
const openGaussSchema = z.object({
|
|
66
|
+
key: keySchema,
|
|
67
|
+
type: z.literal("opengauss"),
|
|
68
|
+
readonly: readonlySchema,
|
|
69
|
+
connection: postgresConnectionSchema
|
|
70
|
+
});
|
|
71
|
+
const redisSchema = z.object({
|
|
72
|
+
key: keySchema,
|
|
73
|
+
type: z.literal("redis"),
|
|
74
|
+
readonly: readonlySchema,
|
|
75
|
+
connection: z
|
|
76
|
+
.object({
|
|
77
|
+
url: z.string().min(1).optional(),
|
|
78
|
+
host: z.string().min(1).optional(),
|
|
79
|
+
port: z.number().int().positive().optional(),
|
|
80
|
+
databaseName: z.number().int().nonnegative().optional(),
|
|
81
|
+
username: z.string().optional(),
|
|
82
|
+
password: z.string().optional(),
|
|
83
|
+
connectTimeoutMs: z.number().int().positive().optional()
|
|
84
|
+
})
|
|
85
|
+
.superRefine((value, context) => {
|
|
86
|
+
if (!value.url && !value.host) {
|
|
87
|
+
context.addIssue({
|
|
88
|
+
code: z.ZodIssueCode.custom,
|
|
89
|
+
message: "Redis connection requires either url or host"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
const databaseArraySchema = z
|
|
95
|
+
.array(z.discriminatedUnion("type", [mysqlSchema, oracleSchema, postgresSchema, openGaussSchema, redisSchema]))
|
|
96
|
+
.min(1);
|
|
97
|
+
const rootConfigSchema = z.object({
|
|
98
|
+
databases: databaseArraySchema,
|
|
99
|
+
logging: z
|
|
100
|
+
.object({
|
|
101
|
+
enabled: z.boolean().default(false),
|
|
102
|
+
directory: z.string().min(1).optional()
|
|
103
|
+
})
|
|
104
|
+
.default({
|
|
105
|
+
enabled: false
|
|
106
|
+
}),
|
|
107
|
+
query: z
|
|
108
|
+
.object({
|
|
109
|
+
timeoutMs: z.number().int().positive().optional()
|
|
110
|
+
})
|
|
111
|
+
.default({})
|
|
112
|
+
}).strict();
|
|
113
|
+
/**
|
|
114
|
+
* Validation happens once at startup. The returned array is already typed and
|
|
115
|
+
* safe for the runtime to consume.
|
|
116
|
+
*/
|
|
117
|
+
export function validateDatabaseConfig(rawConfig) {
|
|
118
|
+
const parsed = rootConfigSchema.safeParse(rawConfig);
|
|
119
|
+
if (!parsed.success) {
|
|
120
|
+
throw new ApplicationError("CONFIG_ERROR", "Invalid database configuration", {
|
|
121
|
+
issues: parsed.error.issues
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const keys = new Set();
|
|
125
|
+
for (const config of parsed.data.databases) {
|
|
126
|
+
if (keys.has(config.key)) {
|
|
127
|
+
throw new ApplicationError("CONFIG_ERROR", `Duplicate database key: ${config.key}`);
|
|
128
|
+
}
|
|
129
|
+
keys.add(config.key);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
databases: parsed.data.databases,
|
|
133
|
+
logging: {
|
|
134
|
+
enabled: parsed.data.logging.enabled,
|
|
135
|
+
directory: parsed.data.logging.directory
|
|
136
|
+
},
|
|
137
|
+
query: {
|
|
138
|
+
timeoutMs: parsed.data.query.timeoutMs
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=configValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configValidation.js","sourceRoot":"","sources":["../../src/config/configValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,cAAc,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;AACnC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAEpC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IACxB,QAAQ,EAAE,cAAc;IACxB,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAC5C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QACxD,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;KAC9D,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACzB,QAAQ,EAAE,cAAc;IACxB,UAAU,EAAE,CAAC;SACV,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAC5C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QACzC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QACxD,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QACvE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;KAC3C,CAAC;SACD,WAAW,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QAC9B,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;YACrC,OAAO,CAAC,QAAQ,CAAC;gBACf,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;gBAC3B,OAAO,EAAE,sDAAsD;aAChE,CAAC,CAAC;QACL,CAAC;QAED,IAAI,KAAK,CAAC,UAAU,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;YACxD,OAAO,CAAC,QAAQ,CAAC;gBACf,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;gBAC3B,OAAO,EAAE,yCAAyC;aACnD,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;CACL,CAAC,CAAC;AAEH,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC5C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACxD,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC9D,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;IAC7B,QAAQ,EAAE,cAAc;IACxB,UAAU,EAAE,wBAAwB;CACrC,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;IAC5B,QAAQ,EAAE,cAAc;IACxB,UAAU,EAAE,wBAAwB;CACrC,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IACxB,QAAQ,EAAE,cAAc;IACxB,UAAU,EAAE,CAAC;SACV,MAAM,CAAC;QACN,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QAClC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAC5C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;QACvD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC/B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC/B,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;KACzD,CAAC;SACD,WAAW,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QAC9B,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAC9B,OAAO,CAAC,QAAQ,CAAC;gBACf,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;gBAC3B,OAAO,EAAE,8CAA8C;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;CACL,CAAC,CAAC;AAEH,MAAM,mBAAmB,GAAG,CAAC;KAC1B,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC;KAC9G,GAAG,CAAC,CAAC,CAAC,CAAC;AAEV,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,SAAS,EAAE,mBAAmB;IAC9B,OAAO,EAAE,CAAC;SACP,MAAM,CAAC;QACN,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;QACnC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;KACxC,CAAC;SACD,OAAO,CAAC;QACP,OAAO,EAAE,KAAK;KACf,CAAC;IACJ,KAAK,EAAE,CAAC;SACL,MAAM,CAAC;QACN,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;KAClD,CAAC;SACD,OAAO,CAAC,EAAE,CAAC;CACf,CAAC,CAAC,MAAM,EAAE,CAAC;AAEZ;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAkB;IACvD,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,gBAAgB,CAAC,cAAc,EAAE,gCAAgC,EAAE;YAC3E,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,gBAAgB,CAAC,cAAc,EAAE,2BAA2B,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QACtF,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAED,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAA6B;QACpD,OAAO,EAAE;YACP,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO;YACpC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS;SACzC;QACD,KAAK,EAAE;YACL,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS;SACvC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LoadedConfig } from "./configTypes.js";
|
|
2
|
+
export declare function resolveConfigPath(argv: string[], env: NodeJS.ProcessEnv): string;
|
|
3
|
+
/**
|
|
4
|
+
* Startup loads configuration once and keeps only validated metadata in memory.
|
|
5
|
+
* No actual database connection is opened here.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadConfigFromPath(configPath: string): Promise<LoadedConfig>;
|
|
8
|
+
export declare function loadConfig(argv: string[], env: NodeJS.ProcessEnv): Promise<LoadedConfig>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ApplicationError } from "../core/errors.js";
|
|
5
|
+
import { configureLogger, log } from "../core/logger.js";
|
|
6
|
+
import { validateDatabaseConfig } from "./configValidation.js";
|
|
7
|
+
export function resolveConfigPath(argv, env) {
|
|
8
|
+
const configIndex = argv.findIndex((value) => value === "--config");
|
|
9
|
+
if (configIndex >= 0) {
|
|
10
|
+
const explicitPath = argv[configIndex + 1];
|
|
11
|
+
if (!explicitPath) {
|
|
12
|
+
throw new ApplicationError("CONFIG_ERROR", "Missing value for --config");
|
|
13
|
+
}
|
|
14
|
+
return path.resolve(explicitPath);
|
|
15
|
+
}
|
|
16
|
+
if (env.MCP_DATABASE_CONFIG) {
|
|
17
|
+
return path.resolve(env.MCP_DATABASE_CONFIG);
|
|
18
|
+
}
|
|
19
|
+
throw new ApplicationError("CONFIG_ERROR", "No configuration path provided. Use --config <path> or MCP_DATABASE_CONFIG.");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Startup loads configuration once and keeps only validated metadata in memory.
|
|
23
|
+
* No actual database connection is opened here.
|
|
24
|
+
*/
|
|
25
|
+
export async function loadConfigFromPath(configPath) {
|
|
26
|
+
const rawText = await readFile(configPath, "utf8");
|
|
27
|
+
let rawConfig;
|
|
28
|
+
try {
|
|
29
|
+
rawConfig = JSON.parse(rawText);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw new ApplicationError("CONFIG_ERROR", "Configuration file is not valid JSON", {
|
|
33
|
+
configPath,
|
|
34
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const validatedConfig = validateDatabaseConfig(rawConfig);
|
|
38
|
+
const databases = validatedConfig.databases;
|
|
39
|
+
const databaseMap = new Map(databases.map((item) => [item.key, item]));
|
|
40
|
+
const loggingDirectory = resolveLoggingDirectory(configPath, validatedConfig.logging.directory);
|
|
41
|
+
configureLogger({
|
|
42
|
+
enabled: validatedConfig.logging.enabled,
|
|
43
|
+
directory: loggingDirectory
|
|
44
|
+
});
|
|
45
|
+
log("info", "Database configuration loaded", {
|
|
46
|
+
configPath,
|
|
47
|
+
count: databases.length,
|
|
48
|
+
logging: {
|
|
49
|
+
enabled: validatedConfig.logging.enabled,
|
|
50
|
+
directory: loggingDirectory
|
|
51
|
+
},
|
|
52
|
+
query: {
|
|
53
|
+
timeoutMs: validatedConfig.query.timeoutMs ?? null
|
|
54
|
+
},
|
|
55
|
+
databases: databases.map((item) => ({
|
|
56
|
+
key: item.key,
|
|
57
|
+
type: item.type,
|
|
58
|
+
readonly: item.readonly
|
|
59
|
+
}))
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
configPath,
|
|
63
|
+
loadedAt: new Date().toISOString(),
|
|
64
|
+
databases,
|
|
65
|
+
databaseMap,
|
|
66
|
+
logging: {
|
|
67
|
+
enabled: validatedConfig.logging.enabled,
|
|
68
|
+
directory: loggingDirectory
|
|
69
|
+
},
|
|
70
|
+
query: {
|
|
71
|
+
timeoutMs: validatedConfig.query.timeoutMs ?? null
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export async function loadConfig(argv, env) {
|
|
76
|
+
const configPath = resolveConfigPath(argv, env);
|
|
77
|
+
return loadConfigFromPath(configPath);
|
|
78
|
+
}
|
|
79
|
+
function resolveLoggingDirectory(configPath, directory) {
|
|
80
|
+
if (!directory) {
|
|
81
|
+
return path.join(os.tmpdir(), "mcp-database-service");
|
|
82
|
+
}
|
|
83
|
+
return path.isAbsolute(directory) ? directory : path.resolve(path.dirname(configPath), directory);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=loadConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadConfig.js","sourceRoot":"","sources":["../../src/config/loadConfig.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ErrorCode = "CONFIG_ERROR" | "DATABASE_NOT_FOUND" | "UNSUPPORTED_DATABASE_TYPE" | "READONLY_VIOLATION" | "INVALID_ARGUMENT" | "NOT_SUPPORTED" | "CONNECTION_ERROR" | "QUERY_ERROR" | "TIMEOUT";
|
|
2
|
+
/**
|
|
3
|
+
* ApplicationError is the single normalized error type used across the service.
|
|
4
|
+
* Wrapping driver-specific exceptions early keeps the MCP layer predictable.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ApplicationError extends Error {
|
|
7
|
+
readonly code: ErrorCode;
|
|
8
|
+
readonly details?: Record<string, unknown>;
|
|
9
|
+
constructor(code: ErrorCode, message: string, details?: Record<string, unknown>);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Any unexpected thrown value is converted into ApplicationError before it crosses
|
|
13
|
+
* a service boundary. This keeps responses stable and easier to debug.
|
|
14
|
+
*/
|
|
15
|
+
export declare function toApplicationError(error: unknown, fallbackCode: ErrorCode): ApplicationError;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApplicationError is the single normalized error type used across the service.
|
|
3
|
+
* Wrapping driver-specific exceptions early keeps the MCP layer predictable.
|
|
4
|
+
*/
|
|
5
|
+
export class ApplicationError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
details;
|
|
8
|
+
constructor(code, message, details) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "ApplicationError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.details = details;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Any unexpected thrown value is converted into ApplicationError before it crosses
|
|
17
|
+
* a service boundary. This keeps responses stable and easier to debug.
|
|
18
|
+
*/
|
|
19
|
+
export function toApplicationError(error, fallbackCode) {
|
|
20
|
+
if (error instanceof ApplicationError) {
|
|
21
|
+
return error;
|
|
22
|
+
}
|
|
23
|
+
if (error instanceof Error) {
|
|
24
|
+
return new ApplicationError(fallbackCode, error.message, { cause: error.name });
|
|
25
|
+
}
|
|
26
|
+
return new ApplicationError(fallbackCode, "Unknown error", { error });
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzB,IAAI,CAAY;IAEhB,OAAO,CAA2B;IAElD,YAAmB,IAAe,EAAE,OAAe,EAAE,OAAiC;QACpF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAc,EAAE,YAAuB;IACxE,IAAI,KAAK,YAAY,gBAAgB,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,IAAI,gBAAgB,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,OAAO,IAAI,gBAAgB,CAAC,YAAY,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* The service uses very small structured logs so operators can inspect what
|
|
5
|
+
* happened without leaking credentials or query payloads.
|
|
6
|
+
*/
|
|
7
|
+
const loggerState = {
|
|
8
|
+
enabled: false,
|
|
9
|
+
directory: "",
|
|
10
|
+
filePath: ""
|
|
11
|
+
};
|
|
12
|
+
export function configureLogger(config) {
|
|
13
|
+
loggerState.enabled = config.enabled;
|
|
14
|
+
loggerState.directory = config.directory;
|
|
15
|
+
loggerState.filePath = path.join(config.directory, "mcp-database-service.log");
|
|
16
|
+
if (config.enabled) {
|
|
17
|
+
mkdirSync(config.directory, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function log(level, message, fields) {
|
|
21
|
+
const payload = {
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
level,
|
|
24
|
+
message,
|
|
25
|
+
...(fields ?? {})
|
|
26
|
+
};
|
|
27
|
+
const line = JSON.stringify(payload);
|
|
28
|
+
console.error(line);
|
|
29
|
+
if (loggerState.enabled) {
|
|
30
|
+
appendFileSync(loggerState.filePath, `${formatFileLog(payload)}\n`, "utf8");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function formatFileLog(payload) {
|
|
34
|
+
const timestamp = String(payload.timestamp ?? "");
|
|
35
|
+
const level = String(payload.level ?? "info").toUpperCase();
|
|
36
|
+
const message = String(payload.message ?? "");
|
|
37
|
+
const extraEntries = Object.entries(payload).filter(([key]) => key !== "timestamp" && key !== "level" && key !== "message");
|
|
38
|
+
const lines = [`[${timestamp}] ${level} ${message}`];
|
|
39
|
+
for (const [key, value] of extraEntries) {
|
|
40
|
+
if (value === undefined) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "string" && value.includes("\n")) {
|
|
44
|
+
lines.push(` ${key}:`);
|
|
45
|
+
for (const line of value.split(/\r?\n/)) {
|
|
46
|
+
lines.push(` ${line}`);
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
lines.push(` ${key}: ${formatFieldValue(value)}`);
|
|
51
|
+
}
|
|
52
|
+
return lines.join("\n");
|
|
53
|
+
}
|
|
54
|
+
function formatFieldValue(value) {
|
|
55
|
+
if (typeof value === "string") {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
59
|
+
return String(value);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return JSON.stringify(value);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return String(value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface PingResult {
|
|
2
|
+
ok: true;
|
|
3
|
+
latencyMs: number;
|
|
4
|
+
}
|
|
5
|
+
export interface SchemaInfo {
|
|
6
|
+
schema: string;
|
|
7
|
+
}
|
|
8
|
+
export interface TableInfo {
|
|
9
|
+
schema: string;
|
|
10
|
+
name: string;
|
|
11
|
+
type: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ColumnInfo {
|
|
14
|
+
name: string;
|
|
15
|
+
dataType: string;
|
|
16
|
+
nullable: boolean;
|
|
17
|
+
defaultValue: string | null;
|
|
18
|
+
comment: string | null;
|
|
19
|
+
primaryKey: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface QueryResult {
|
|
22
|
+
rowCount: number;
|
|
23
|
+
rows: Record<string, unknown>[];
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface IndexInfo {
|
|
27
|
+
schema: string;
|
|
28
|
+
tableName: string;
|
|
29
|
+
indexName: string;
|
|
30
|
+
columnName: string | null;
|
|
31
|
+
isUnique: boolean | null;
|
|
32
|
+
columnPosition: number | null;
|
|
33
|
+
sortOrder: string | null;
|
|
34
|
+
indexType: string | null;
|
|
35
|
+
definition: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface TableStatistics {
|
|
38
|
+
schema: string;
|
|
39
|
+
tableName: string;
|
|
40
|
+
metrics: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
export interface StatementResult {
|
|
43
|
+
command: string;
|
|
44
|
+
affectedRows: number | null;
|
|
45
|
+
}
|
|
46
|
+
export interface RedisScanResult {
|
|
47
|
+
nextCursor: string;
|
|
48
|
+
keys: string[];
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resultTypes.js","sourceRoot":"","sources":["../../src/core/resultTypes.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DatabaseConfig } from "../config/configTypes.js";
|
|
2
|
+
import type { DatabaseAdapter } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Adapters are created per request so each tool call owns its own connection
|
|
5
|
+
* lifecycle and can clean up independently.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createClient(config: DatabaseConfig, options?: {
|
|
8
|
+
queryTimeoutMs?: number | null;
|
|
9
|
+
}): DatabaseAdapter;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ApplicationError } from "../core/errors.js";
|
|
2
|
+
import { RedisAdapter } from "./redis/redisClient.js";
|
|
3
|
+
import { MysqlAdapter } from "./sql/mysqlClient.js";
|
|
4
|
+
import { OpenGaussAdapter } from "./sql/openGaussClient.js";
|
|
5
|
+
import { OracleAdapter } from "./sql/oracleClient.js";
|
|
6
|
+
import { PostgresAdapter } from "./sql/postgresClient.js";
|
|
7
|
+
/**
|
|
8
|
+
* Adapters are created per request so each tool call owns its own connection
|
|
9
|
+
* lifecycle and can clean up independently.
|
|
10
|
+
*/
|
|
11
|
+
export function createClient(config, options) {
|
|
12
|
+
switch (config.type) {
|
|
13
|
+
case "mysql":
|
|
14
|
+
return new MysqlAdapter(config, options?.queryTimeoutMs ?? null);
|
|
15
|
+
case "oracle":
|
|
16
|
+
return new OracleAdapter(config, options?.queryTimeoutMs ?? null);
|
|
17
|
+
case "postgresql":
|
|
18
|
+
return new PostgresAdapter(config, options?.queryTimeoutMs ?? null);
|
|
19
|
+
case "opengauss":
|
|
20
|
+
return new OpenGaussAdapter(config, options?.queryTimeoutMs ?? null);
|
|
21
|
+
case "redis":
|
|
22
|
+
return new RedisAdapter(config, options?.queryTimeoutMs ?? null);
|
|
23
|
+
default:
|
|
24
|
+
throw new ApplicationError("UNSUPPORTED_DATABASE_TYPE", `Unsupported database type: ${config.type}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=clientFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientFactory.js","sourceRoot":"","sources":["../../src/db/clientFactory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAsB,EACtB,OAEC;IAED,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,OAAO;YACV,OAAO,IAAI,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC,CAAC;QACnE,KAAK,QAAQ;YACX,OAAO,IAAI,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC,CAAC;QACpE,KAAK,YAAY;YACf,OAAO,IAAI,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC,CAAC;QACtE,KAAK,WAAW;YACd,OAAO,IAAI,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC,CAAC;QACvE,KAAK,OAAO;YACV,OAAO,IAAI,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC,CAAC;QACnE;YACE,MAAM,IAAI,gBAAgB,CAAC,2BAA2B,EAAE,8BAA+B,MAAyB,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7H,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface SqlStatementInfo {
|
|
2
|
+
normalizedSql: string;
|
|
3
|
+
firstKeyword: string;
|
|
4
|
+
isReadonlyQuery: boolean;
|
|
5
|
+
hasWhereClause: boolean;
|
|
6
|
+
riskLevel: "normal" | "high" | "critical";
|
|
7
|
+
riskReasons: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* SQL validation is intentionally conservative. The service is for read-only
|
|
11
|
+
* access, so a false negative is safer than accidentally allowing a write.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertReadonlySql(sql: string): void;
|
|
14
|
+
export declare function inspectSqlStatement(sql: string): SqlStatementInfo;
|