taurusdb-core 0.1.0 → 0.2.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.
@@ -17,18 +17,18 @@ export function parseEnvProfile(env) {
17
17
  let host;
18
18
  let port;
19
19
  let database;
20
- let readonlyUsername;
21
- let readonlyPasswordRef;
20
+ let username;
21
+ let passwordRef;
22
22
  if (dsn) {
23
23
  const url = new URL(dsn);
24
24
  engine = parseEngineFromDsnProtocol(url.protocol);
25
25
  host = url.hostname;
26
26
  port = url.port ? Number.parseInt(url.port, 10) : defaultPortForEngine(engine);
27
27
  database = asString(url.pathname.replace(/^\//, ""));
28
- readonlyUsername = asString(decodeURIComponent(url.username));
28
+ username = asString(decodeURIComponent(url.username));
29
29
  const dsnPassword = asString(decodeURIComponent(url.password));
30
30
  if (dsnPassword) {
31
- readonlyPasswordRef = parseCredentialRef(dsnPassword, "TAURUSDB_SQL_DSN.password");
31
+ passwordRef = parseCredentialRef(dsnPassword, "TAURUSDB_SQL_DSN.password");
32
32
  }
33
33
  }
34
34
  else {
@@ -38,41 +38,27 @@ export function parseEnvProfile(env) {
38
38
  port = explicitPort ?? defaultPortForEngine(engine);
39
39
  database = asString(env.TAURUSDB_SQL_DATABASE);
40
40
  }
41
- const mutationUserName = asString(env.TAURUSDB_SQL_MUTATION_USER);
42
- const mutationPasswordRaw = asString(env.TAURUSDB_SQL_MUTATION_PASSWORD);
43
41
  if (!dsn &&
44
42
  !explicitHost &&
45
43
  !asString(env.TAURUSDB_SQL_USER) &&
46
44
  !asString(env.TAURUSDB_SQL_PASSWORD) &&
47
- !database &&
48
- !mutationUserName &&
49
- !mutationPasswordRaw) {
45
+ !database) {
50
46
  return undefined;
51
47
  }
52
48
  if (!port || !Number.isFinite(port) || port <= 0) {
53
49
  throw new Error("Failed to resolve SQL port from environment.");
54
50
  }
55
- readonlyUsername = readonlyUsername ?? asString(env.TAURUSDB_SQL_USER);
56
- if (!readonlyUsername) {
57
- throw new Error("Missing readonly username in environment. Set TAURUSDB_SQL_USER or include it in DSN.");
51
+ username = username ?? asString(env.TAURUSDB_SQL_USER);
52
+ if (!username) {
53
+ throw new Error("Missing SQL username in environment. Set TAURUSDB_SQL_USER or include it in DSN.");
58
54
  }
59
- readonlyPasswordRef =
60
- readonlyPasswordRef ??
55
+ passwordRef =
56
+ passwordRef ??
61
57
  (Object.hasOwn(env, "TAURUSDB_SQL_PASSWORD")
62
58
  ? parseCredentialRef(env.TAURUSDB_SQL_PASSWORD, "TAURUSDB_SQL_PASSWORD")
63
59
  : undefined);
64
- if (!readonlyPasswordRef) {
65
- throw new Error("Missing readonly password in environment. Set TAURUSDB_SQL_PASSWORD or include it in DSN.");
66
- }
67
- let mutationUser;
68
- if (mutationUserName || mutationPasswordRaw) {
69
- if (!mutationUserName || !mutationPasswordRaw) {
70
- throw new Error("Invalid mutation credentials in environment: TAURUSDB_SQL_MUTATION_USER and TAURUSDB_SQL_MUTATION_PASSWORD must be set together.");
71
- }
72
- mutationUser = {
73
- username: mutationUserName,
74
- password: parseCredentialRef(mutationPasswordRaw, "TAURUSDB_SQL_MUTATION_PASSWORD"),
75
- };
60
+ if (!passwordRef) {
61
+ throw new Error("Missing SQL password in environment. Set TAURUSDB_SQL_PASSWORD or include it in DSN.");
76
62
  }
77
63
  const poolSize = asInteger(env.TAURUSDB_SQL_POOL_SIZE);
78
64
  if (poolSize !== undefined && poolSize <= 0) {
@@ -84,11 +70,10 @@ export function parseEnvProfile(env) {
84
70
  host,
85
71
  port,
86
72
  database,
87
- readonlyUser: {
88
- username: readonlyUsername,
89
- password: readonlyPasswordRef,
73
+ user: {
74
+ username,
75
+ password: passwordRef,
90
76
  },
91
- mutationUser,
92
77
  poolSize,
93
78
  });
94
79
  }
@@ -194,13 +194,15 @@ export function parseProfileRecord(name, value, context) {
194
194
  if (poolSize !== undefined && poolSize <= 0) {
195
195
  throw new Error(`Invalid datasource profile ${name} in ${context}: poolSize must be positive.`);
196
196
  }
197
- const readonlyRaw = value.readonlyUser ?? value.readonly ?? value.readOnlyUser;
198
- if (readonlyRaw === undefined) {
199
- throw new Error(`Invalid datasource profile ${name} in ${context}: missing readonlyUser.`);
200
- }
201
- const readonlyUser = parseUserCredential(readonlyRaw, `${context}.${name}.readonlyUser`);
202
- const mutationRaw = value.mutationUser ?? value.writeUser ?? value.rwUser;
203
- const mutationUser = mutationRaw !== undefined ? parseUserCredential(mutationRaw, `${context}.${name}.mutationUser`) : undefined;
197
+ // Backward compatibility for older profile field names. New configs should use `user`.
198
+ const userRaw = value.user ??
199
+ value.readonlyUser ??
200
+ value.readonly ??
201
+ value.readOnlyUser;
202
+ if (userRaw === undefined) {
203
+ throw new Error(`Invalid datasource profile ${name} in ${context}: missing user.`);
204
+ }
205
+ const user = parseUserCredential(userRaw, `${context}.${name}.user`);
204
206
  const tls = parseTlsOptions(value.tls, `${context}.${name}.tls`);
205
207
  return withRedactedToString({
206
208
  name,
@@ -208,8 +210,7 @@ export function parseProfileRecord(name, value, context) {
208
210
  host,
209
211
  port,
210
212
  database,
211
- readonlyUser,
212
- mutationUser,
213
+ user,
213
214
  tls,
214
215
  poolSize,
215
216
  });
@@ -5,8 +5,10 @@ export function applyRuntimeTarget(profile, target) {
5
5
  }
6
6
  return withRedactedToString({
7
7
  ...profile,
8
- host: target.host,
8
+ host: target.host ?? profile.host,
9
9
  port: target.port ?? profile.port,
10
+ database: target.database ?? profile.database,
11
+ user: target.user ?? profile.user,
10
12
  });
11
13
  }
12
14
  export class RuntimeOverrideProfileLoader {
@@ -16,12 +18,16 @@ export class RuntimeOverrideProfileLoader {
16
18
  this.base = base;
17
19
  }
18
20
  setRuntimeTarget(name, target) {
19
- this.runtimeTargets.set(name, {
20
- host: target.host,
21
- port: target.port,
22
- instanceId: target.instanceId,
23
- nodeId: target.nodeId,
24
- });
21
+ const current = this.runtimeTargets.get(name);
22
+ const next = {
23
+ host: target.host ?? current?.host,
24
+ port: target.port ?? current?.port,
25
+ database: target.database ?? current?.database,
26
+ user: target.user ?? current?.user,
27
+ instanceId: target.instanceId ?? current?.instanceId,
28
+ nodeId: target.nodeId ?? current?.nodeId,
29
+ };
30
+ this.runtimeTargets.set(name, next);
25
31
  }
26
32
  clearRuntimeTarget(name) {
27
33
  this.runtimeTargets.delete(name);
@@ -31,8 +31,7 @@ export interface DataSourceProfile {
31
31
  host?: string;
32
32
  port: number;
33
33
  database?: string;
34
- readonlyUser: UserCredential;
35
- mutationUser?: UserCredential;
34
+ user: UserCredential;
36
35
  tls?: TlsOptions;
37
36
  poolSize?: number;
38
37
  toString(): string;
@@ -43,8 +42,10 @@ export interface ProfileLoader {
43
42
  get(name: string): Promise<DataSourceProfile | undefined>;
44
43
  }
45
44
  export interface RuntimeDataSourceTarget {
46
- host: string;
45
+ host?: string;
47
46
  port?: number;
47
+ database?: string;
48
+ user?: UserCredential;
48
49
  instanceId?: string;
49
50
  nodeId?: string;
50
51
  }
@@ -41,9 +41,11 @@ export declare class UnsupportedFeatureError extends Error {
41
41
  readonly feature: TaurusFeatureName;
42
42
  readonly requiredVersion?: string;
43
43
  readonly currentVersion?: string;
44
+ readonly parameterHint?: string;
44
45
  constructor(feature: TaurusFeatureName, message: string, options?: {
45
46
  requiredVersion?: string;
46
47
  currentVersion?: string;
48
+ parameterHint?: string;
47
49
  cause?: unknown;
48
50
  });
49
51
  }
@@ -3,12 +3,14 @@ export class UnsupportedFeatureError extends Error {
3
3
  feature;
4
4
  requiredVersion;
5
5
  currentVersion;
6
+ parameterHint;
6
7
  constructor(feature, message, options = {}) {
7
8
  super(message);
8
9
  this.name = "UnsupportedFeatureError";
9
10
  this.feature = feature;
10
11
  this.requiredVersion = options.requiredVersion;
11
12
  this.currentVersion = options.currentVersion;
13
+ this.parameterHint = options.parameterHint;
12
14
  if (options.cause !== undefined) {
13
15
  this.cause = options.cause;
14
16
  }
@@ -120,7 +120,6 @@ export function buildRawConfigFromEnv(env) {
120
120
  return {
121
121
  defaultDatasource: readString(env.TAURUSDB_DEFAULT_DATASOURCE) ?? inferredDatasourceName,
122
122
  profilesPath: expandTildePath(readString(env.TAURUSDB_SQL_PROFILES)),
123
- enableMutations: parseBoolean(env.TAURUSDB_MCP_ENABLE_MUTATIONS, "TAURUSDB_MCP_ENABLE_MUTATIONS"),
124
123
  cloud: {
125
124
  provider: "huaweicloud",
126
125
  region: cloudRegion,
@@ -2,7 +2,6 @@ import { z } from "zod";
2
2
  export declare const ConfigSchema: z.ZodObject<{
3
3
  defaultDatasource: z.ZodOptional<z.ZodString>;
4
4
  profilesPath: z.ZodOptional<z.ZodString>;
5
- enableMutations: z.ZodDefault<z.ZodBoolean>;
6
5
  cloud: z.ZodDefault<z.ZodObject<{
7
6
  provider: z.ZodDefault<z.ZodEnum<["huaweicloud"]>>;
8
7
  region: z.ZodOptional<z.ZodString>;
@@ -270,7 +269,6 @@ export declare const ConfigSchema: z.ZodObject<{
270
269
  } | undefined;
271
270
  }>>;
272
271
  }, "strip", z.ZodTypeAny, {
273
- enableMutations: boolean;
274
272
  cloud: {
275
273
  provider: "huaweicloud";
276
274
  domainSuffix: string;
@@ -344,7 +342,6 @@ export declare const ConfigSchema: z.ZodObject<{
344
342
  }, {
345
343
  defaultDatasource?: string | undefined;
346
344
  profilesPath?: string | undefined;
347
- enableMutations?: boolean | undefined;
348
345
  cloud?: {
349
346
  provider?: "huaweicloud" | undefined;
350
347
  region?: string | undefined;
@@ -82,7 +82,6 @@ const CesMetricsSourceSchema = z
82
82
  export const ConfigSchema = z.object({
83
83
  defaultDatasource: z.string().min(1).optional(),
84
84
  profilesPath: z.string().min(1).optional(),
85
- enableMutations: z.boolean().default(true),
86
85
  cloud: CloudSchema,
87
86
  limits: LimitsSchema,
88
87
  audit: AuditSchema,
@@ -286,6 +286,7 @@ export async function flashbackQuery(engine, input, ctx, opts) {
286
286
  throw new UnsupportedFeatureError("flashback_query", flashbackFeature.reason ??
287
287
  `Flashback query requires kernel version >= ${flashbackFeature.minVersion ?? "unknown"}.`, {
288
288
  requiredVersion: flashbackFeature.minVersion,
289
+ parameterHint: flashbackFeature.param,
289
290
  currentVersion: (await engine.capabilityProbe.getKernelInfo(ctx))
290
291
  .kernelVersion,
291
292
  });
@@ -318,6 +319,7 @@ export async function listRecycleBin(engine, ctx, opts) {
318
319
  throw new UnsupportedFeatureError("recycle_bin", recycleBinFeature.reason ??
319
320
  `Recycle bin requires kernel version >= ${recycleBinFeature.minVersion ?? "unknown"}.`, {
320
321
  requiredVersion: recycleBinFeature.minVersion,
322
+ parameterHint: recycleBinFeature.param,
321
323
  currentVersion: (await engine.capabilityProbe.getKernelInfo(ctx))
322
324
  .kernelVersion,
323
325
  });
@@ -331,13 +333,13 @@ export async function restoreRecycleBinTable(engine, input, ctx, opts) {
331
333
  throw new UnsupportedFeatureError("recycle_bin", recycleBinFeature.reason ??
332
334
  `Recycle bin requires kernel version >= ${recycleBinFeature.minVersion ?? "unknown"}.`, {
333
335
  requiredVersion: recycleBinFeature.minVersion,
336
+ parameterHint: recycleBinFeature.param,
334
337
  currentVersion: (await engine.capabilityProbe.getKernelInfo(ctx))
335
338
  .kernelVersion,
336
339
  });
337
340
  }
338
341
  return engine.executor.executeMutation(buildRestoreRecycleBinTableSql(input), ctx, {
339
342
  ...recycleBinMutationOptions(opts),
340
- allowWithoutGlobalMutations: true,
341
343
  allowReadonlyFallbackForMutations: true,
342
344
  });
343
345
  }
@@ -17,7 +17,6 @@ export interface DataSourceInfo {
17
17
  host?: string;
18
18
  port: number;
19
19
  database?: string;
20
- hasMutationUser: boolean;
21
20
  poolSize?: number;
22
21
  isDefault: boolean;
23
22
  }
package/dist/engine.js CHANGED
@@ -22,7 +22,6 @@ function toDataSourceInfo(profile, defaultDatasource) {
22
22
  host: profile.host,
23
23
  port: profile.port,
24
24
  database: profile.database,
25
- hasMutationUser: profile.mutationUser !== undefined,
26
25
  poolSize: profile.poolSize,
27
26
  isDefault: profile.name === defaultDatasource,
28
27
  };
@@ -35,7 +35,6 @@ export interface PoolHealth {
35
35
  }
36
36
  export interface ConnectionPool {
37
37
  acquire(datasource: string, mode: PoolMode, opts?: {
38
- allowWithoutGlobalMutations?: boolean;
39
38
  allowReadonlyFallbackForMutations?: boolean;
40
39
  }): Promise<Session>;
41
40
  release(session: Session): Promise<void>;
@@ -92,7 +91,6 @@ export declare class ConnectionPoolManager implements ConnectionPool {
92
91
  private readonly activeSessions;
93
92
  constructor(options: ConnectionPoolManagerOptions);
94
93
  acquire(datasource: string, mode: PoolMode, opts?: {
95
- allowWithoutGlobalMutations?: boolean;
96
94
  allowReadonlyFallbackForMutations?: boolean;
97
95
  }): Promise<Session>;
98
96
  release(session: Session): Promise<void>;
@@ -32,22 +32,8 @@ async function resolveTls(tls, secretResolver) {
32
32
  }
33
33
  return resolved;
34
34
  }
35
- function ensureMutationAllowed(config) {
36
- if (!config.enableMutations) {
37
- throw new ConnectionPoolError("Mutation mode is disabled by configuration.");
38
- }
39
- }
40
- function selectCredential(profile, mode, opts = {}) {
41
- if (mode === "ro") {
42
- return profile.readonlyUser;
43
- }
44
- if (!profile.mutationUser) {
45
- if (opts.allowReadonlyFallbackForMutations) {
46
- return profile.readonlyUser;
47
- }
48
- throw new ConnectionPoolError(`Mutation user is not configured for datasource "${profile.name}".`);
49
- }
50
- return profile.mutationUser;
35
+ function selectCredential(profile, mode, _opts = {}) {
36
+ return profile.user;
51
37
  }
52
38
  async function resolveCredentialValue(ref, secretResolver, context) {
53
39
  try {
@@ -75,9 +61,6 @@ export class ConnectionPoolManager {
75
61
  if (!profile) {
76
62
  throw new ConnectionPoolError(`Datasource profile not found: "${datasource}".`);
77
63
  }
78
- if (mode === "rw" && !opts.allowWithoutGlobalMutations) {
79
- ensureMutationAllowed(this.config);
80
- }
81
64
  const entry = await this.getOrCreatePool(profile, mode, opts);
82
65
  let driverSession;
83
66
  try {
@@ -122,16 +105,7 @@ export class ConnectionPoolManager {
122
105
  async healthCheck(datasource) {
123
106
  const modes = [];
124
107
  modes.push(await this.healthCheckMode(datasource, "ro"));
125
- if (!this.config.enableMutations) {
126
- modes.push({
127
- mode: "rw",
128
- status: "skipped",
129
- message: "Mutation mode disabled by config.",
130
- });
131
- }
132
- else {
133
- modes.push(await this.healthCheckMode(datasource, "rw"));
134
- }
108
+ modes.push(await this.healthCheckMode(datasource, "rw"));
135
109
  return {
136
110
  datasource,
137
111
  checkedAt: new Date().toISOString(),
@@ -166,9 +140,6 @@ export class ConnectionPoolManager {
166
140
  return { mode, status: "ok" };
167
141
  }
168
142
  catch (error) {
169
- if (mode === "rw" && error instanceof ConnectionPoolError && /not configured/.test(error.message)) {
170
- return { mode, status: "skipped", message: error.message };
171
- }
172
143
  return {
173
144
  mode,
174
145
  status: "error",
@@ -114,7 +114,6 @@ export class SqlExecutorImpl {
114
114
  const startedAt = this.now();
115
115
  const timeoutMs = opts.timeoutMs ?? ctx.limits.timeoutMs;
116
116
  const session = await this.connectionPool.acquire(ctx.datasource, "rw", {
117
- allowWithoutGlobalMutations: opts.allowWithoutGlobalMutations,
118
117
  allowReadonlyFallbackForMutations: opts.allowReadonlyFallbackForMutations,
119
118
  });
120
119
  const active = this.beginQuery(queryId, session, ctx, "rw", startedAt);
@@ -15,7 +15,6 @@ export interface ReadonlyOptions {
15
15
  }
16
16
  export interface MutationOptions {
17
17
  timeoutMs?: number;
18
- allowWithoutGlobalMutations?: boolean;
19
18
  allowReadonlyFallbackForMutations?: boolean;
20
19
  }
21
20
  export interface QueryResult {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taurusdb-core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared TaurusDB data-plane engine for schema, SQL execution, guardrails, and diagnostics.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",