@rex0220/kintone-sql-tools 1.12.1 → 1.13.1

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.
@@ -37666,195 +37666,169 @@ function buildBatchEnvelope(batch, options = {}) {
37666
37666
  };
37667
37667
  }
37668
37668
 
37669
- // src/node/appProfiles.ts
37670
- function parseTokenMap(raw) {
37671
- const out = {};
37672
- if (!raw.trim()) return out;
37673
- const pairs = raw.split(",");
37674
- for (const pair of pairs) {
37675
- const idx = pair.indexOf("=");
37676
- if (idx <= 0) throw new Error("ArgumentError: --token-map must be APPxxx=token pairs.");
37677
- const key = normalizeAppKey(pair.slice(0, idx).trim());
37678
- const value = pair.slice(idx + 1).trim();
37679
- if (!value) throw new Error(`ArgumentError: token is empty for ${key}.`);
37680
- out[key] = value;
37669
+ // src/node/sqlDiagnostics.ts
37670
+ function restoreSqlDiagnosticValue(value, bindings) {
37671
+ if (typeof value === "string") {
37672
+ let restored = value;
37673
+ for (const binding of bindings.values()) {
37674
+ const internal = `APP${binding.mappedAppId}`;
37675
+ const display = binding.source === "logical" ? `LAPP_${binding.logicalName}@${binding.profile}` : `APP${binding.appId}@${binding.profile}`;
37676
+ restored = restored.split(`${internal} (${binding.mappedAppId})`).join(display).split(internal).join(display);
37677
+ }
37678
+ return restored;
37681
37679
  }
37682
- return out;
37683
- }
37684
- function normalizeAppKey(v) {
37685
- const m1 = v.match(/^APP(\d+)$/i);
37686
- if (m1) return `APP${m1[1]}`;
37687
- const m2 = v.match(/^(\d+)$/);
37688
- if (m2) return `APP${m2[1]}`;
37689
- throw new Error(`ArgumentError: invalid app key "${v}"`);
37690
- }
37691
- function extractAppIds(sql) {
37692
- const out = /* @__PURE__ */ new Set();
37693
- for (const t of collectAppProfileTokens(sql)) out.add(t.appId);
37694
- return [...out];
37695
- }
37696
- function isSqlIdentContinue(ch) {
37697
- if (!ch) return false;
37698
- const cp = ch.codePointAt(0);
37699
- return cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp >= 48 && cp <= 57 || cp === 95 || cp === 36 || cp >= 12352 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65281 && cp <= 65376;
37700
- }
37701
- function isProfileNameChar(ch) {
37702
- if (!ch) return false;
37703
- const cp = ch.codePointAt(0);
37704
- return cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp >= 48 && cp <= 57 || ch === "_" || ch === "-" || ch === "." || ch === "$";
37705
- }
37706
- function tryParseAppProfileToken(sql, start) {
37707
- const head = sql.slice(start, start + 3);
37708
- if (head.toUpperCase() !== "APP") return null;
37709
- const prev = start > 0 ? sql[start - 1] : "";
37710
- if (isSqlIdentContinue(prev)) return null;
37711
- let i = start + 3;
37712
- const digitStart = i;
37713
- while (i < sql.length && /[0-9]/.test(sql[i])) i++;
37714
- const digitEnd = i;
37715
- if (digitEnd === digitStart) return null;
37716
- if (sql[i] === "$") {
37717
- i++;
37718
- const subStart = i;
37719
- while (i < sql.length && isSqlIdentContinue(sql[i])) i++;
37720
- if (i === subStart) return null;
37680
+ if (Array.isArray(value)) {
37681
+ return value.map((item) => restoreSqlDiagnosticValue(item, bindings));
37721
37682
  }
37722
- const appEnd = i;
37723
- let profile2 = null;
37724
- if (sql[i] === "@") {
37725
- i++;
37726
- const pStart = i;
37727
- while (i < sql.length && isProfileNameChar(sql[i])) i++;
37728
- if (i === pStart) return null;
37729
- profile2 = sql.slice(pStart, i);
37683
+ if (value !== null && typeof value === "object") {
37684
+ return Object.fromEntries(
37685
+ Object.entries(value).map(([key, item]) => [key, restoreSqlDiagnosticValue(item, bindings)])
37686
+ );
37730
37687
  }
37731
- const next = i < sql.length ? sql[i] : "";
37732
- if (isSqlIdentContinue(next)) return null;
37733
- return {
37734
- appId: Number(sql.slice(digitStart, digitEnd)),
37735
- profile: profile2,
37736
- start,
37737
- digitStart,
37738
- digitEnd,
37739
- appEnd,
37740
- fullEnd: i
37741
- };
37688
+ return value;
37742
37689
  }
37743
- function collectAppProfileTokens(sql) {
37744
- const tokens = [];
37745
- let i = 0;
37746
- while (i < sql.length) {
37747
- const ch = sql[i];
37748
- if (ch === "'") {
37749
- i++;
37750
- while (i < sql.length) {
37751
- if (sql[i] === "'") {
37752
- i++;
37753
- if (i < sql.length && sql[i] === "'") {
37754
- i++;
37755
- continue;
37756
- }
37757
- break;
37758
- }
37759
- i++;
37690
+ function restoreSqlContextError(err, sourceSql, context) {
37691
+ if (!(err instanceof Error)) return err;
37692
+ let message = err.message;
37693
+ for (const binding of context.bindings.values()) {
37694
+ if (binding.source === "logical") {
37695
+ message = message.split(`APP${binding.mappedAppId}`).join(`LAPP_${binding.logicalName}@${binding.profile}`);
37696
+ }
37697
+ }
37698
+ const token = err.token;
37699
+ if (typeof token?.pos === "number") {
37700
+ const segment = context.rewriteSegments.find(
37701
+ (candidate) => token.pos >= candidate.normalizedStart && token.pos < candidate.normalizedEnd
37702
+ );
37703
+ if (segment) {
37704
+ const sourcePos = segment.sourceStart + Math.min(
37705
+ token.pos - segment.normalizedStart,
37706
+ Math.max(0, segment.sourceEnd - segment.sourceStart - 1)
37707
+ );
37708
+ message = message.replace(/(位置 \d+、トークン:/, `\uFF08\u4F4D\u7F6E ${sourcePos}\u3001\u30C8\u30FC\u30AF\u30F3:`);
37709
+ const originalRef = sourceSql.slice(segment.sourceStart, segment.sourceEnd);
37710
+ if (segment.bindingMappedAppId !== void 0 && originalRef) {
37711
+ message = message.replace(/(トークン: 「)[^」]*(」)/, `$1${originalRef}$2`);
37760
37712
  }
37761
- continue;
37762
37713
  }
37763
- if (ch === "`") {
37764
- i++;
37765
- while (i < sql.length && sql[i] !== "`") i++;
37766
- if (i < sql.length) i++;
37767
- continue;
37714
+ }
37715
+ err.message = message;
37716
+ return err;
37717
+ }
37718
+
37719
+ // src/node/config.ts
37720
+ var import_fs = require("fs");
37721
+ var LOGICAL_APP_NAME_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
37722
+ var PHYSICAL_APP_KEY_RE = /^APP\d+$/i;
37723
+ var NUMERIC_APP_KEY_RE = /^\d+$/;
37724
+ var LOGICAL_SQL_KEY_RE = /^LAPP_/i;
37725
+ function argumentError(message) {
37726
+ return new Error(`ArgumentError: ${message}`);
37727
+ }
37728
+ function normalizeLogicalApps(profileName, value) {
37729
+ if (value === void 0) return void 0;
37730
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
37731
+ throw argumentError(`logicalApps for profile "${profileName}" must be an object.`);
37732
+ }
37733
+ const normalized = {};
37734
+ const physicalIdOwners = /* @__PURE__ */ new Map();
37735
+ for (const [rawName, rawAppId] of Object.entries(value)) {
37736
+ if (PHYSICAL_APP_KEY_RE.test(rawName) || NUMERIC_APP_KEY_RE.test(rawName) || LOGICAL_SQL_KEY_RE.test(rawName)) {
37737
+ throw argumentError(
37738
+ `logical app key "${rawName}" in profile "${profileName}" must be a logical name without APP, numeric, or LAPP_ syntax.`
37739
+ );
37768
37740
  }
37769
- if (ch === "-" && sql[i + 1] === "-") {
37770
- i += 2;
37771
- while (i < sql.length && sql[i] !== "\n") i++;
37772
- continue;
37741
+ if (!LOGICAL_APP_NAME_RE.test(rawName)) {
37742
+ throw argumentError(
37743
+ `logical app key "${rawName}" in profile "${profileName}" must match [A-Z][A-Z0-9_]{0,63}.`
37744
+ );
37773
37745
  }
37774
- if (ch === "/" && sql[i + 1] === "*") {
37775
- i += 2;
37776
- while (i < sql.length) {
37777
- if (sql[i] === "*" && sql[i + 1] === "/") {
37778
- i += 2;
37779
- break;
37780
- }
37781
- i++;
37782
- }
37783
- continue;
37746
+ const logicalName = rawName.toUpperCase();
37747
+ if (Object.prototype.hasOwnProperty.call(normalized, logicalName)) {
37748
+ throw argumentError(
37749
+ `logical app name "${logicalName}" is duplicated after case normalization in profile "${profileName}".`
37750
+ );
37784
37751
  }
37785
- const parsed = tryParseAppProfileToken(sql, i);
37786
- if (!parsed) {
37787
- i++;
37788
- continue;
37752
+ if (typeof rawAppId !== "number" || !Number.isSafeInteger(rawAppId) || rawAppId <= 0) {
37753
+ throw argumentError(
37754
+ `physical app ID for logical app "${logicalName}" in profile "${profileName}" must be a positive safe integer.`
37755
+ );
37789
37756
  }
37790
- tokens.push(parsed);
37791
- i = parsed.fullEnd;
37757
+ const existingName = physicalIdOwners.get(rawAppId);
37758
+ if (existingName !== void 0) {
37759
+ throw argumentError(
37760
+ `logical apps "${existingName}" and "${logicalName}" in profile "${profileName}" map to the same physical app ID ${rawAppId}; physical app aliases are not supported yet.`
37761
+ );
37762
+ }
37763
+ physicalIdOwners.set(rawAppId, logicalName);
37764
+ normalized[logicalName] = rawAppId;
37792
37765
  }
37793
- return tokens;
37766
+ return normalized;
37794
37767
  }
37795
- function nextVirtualAppId(used) {
37796
- let id = 9e8;
37797
- while (used.has(id)) id++;
37798
- used.add(id);
37799
- return id;
37800
- }
37801
- function normalizeSqlAppProfiles(sql, defaultProfile = "dev") {
37802
- const tokens = collectAppProfileTokens(sql);
37803
- const hasProfileSyntax = tokens.some((t) => t.profile !== null);
37804
- const profilesByApp = /* @__PURE__ */ new Map();
37805
- const normalizedProfile = (profile2) => profile2 ?? defaultProfile;
37806
- for (const t of tokens) {
37807
- const p = normalizedProfile(t.profile);
37808
- let set2 = profilesByApp.get(t.appId);
37809
- if (!set2) {
37810
- set2 = /* @__PURE__ */ new Set();
37811
- profilesByApp.set(t.appId, set2);
37812
- }
37813
- set2.add(p.toLowerCase());
37768
+ function validateKsqlConfig(config2) {
37769
+ if (config2 === null || typeof config2 !== "object" || Array.isArray(config2)) {
37770
+ throw argumentError("config must be an object.");
37814
37771
  }
37815
- const usedAppIds = new Set(tokens.map((t) => t.appId));
37816
- const pairToMapped = /* @__PURE__ */ new Map();
37817
- const appBindingByMappedApp = /* @__PURE__ */ new Map();
37818
- for (const [appId, pSet] of profilesByApp.entries()) {
37819
- const profiles = [...pSet].sort();
37820
- if (profiles.length <= 1) continue;
37821
- for (const pLower of profiles) {
37822
- const mapped = nextVirtualAppId(usedAppIds);
37823
- pairToMapped.set(`${appId}@${pLower}`, mapped);
37824
- appBindingByMappedApp.set(mapped, { appId, profile: pLower });
37772
+ if (config2.profiles === void 0) return config2;
37773
+ if (config2.profiles === null || typeof config2.profiles !== "object" || Array.isArray(config2.profiles)) {
37774
+ throw argumentError("profiles must be an object.");
37775
+ }
37776
+ for (const [profileName, profile2] of Object.entries(config2.profiles)) {
37777
+ if (profile2 === null || typeof profile2 !== "object" || Array.isArray(profile2)) {
37778
+ throw argumentError(`profile "${profileName}" must be an object.`);
37825
37779
  }
37780
+ if (profile2.allowPhysicalAppRefs !== void 0 && typeof profile2.allowPhysicalAppRefs !== "boolean") {
37781
+ throw argumentError(`allowPhysicalAppRefs for profile "${profileName}" must be boolean.`);
37782
+ }
37783
+ const logicalApps = normalizeLogicalApps(profileName, profile2.logicalApps);
37784
+ if (logicalApps !== void 0) profile2.logicalApps = logicalApps;
37826
37785
  }
37827
- const out = [];
37828
- let cursor = 0;
37829
- for (const t of tokens) {
37830
- const p = normalizedProfile(t.profile);
37831
- const pLower = p.toLowerCase();
37832
- const mapped = pairToMapped.get(`${t.appId}@${pLower}`) ?? t.appId;
37833
- appBindingByMappedApp.set(mapped, { appId: t.appId, profile: pLower });
37834
- out.push(sql.slice(cursor, t.start));
37835
- out.push(sql.slice(t.start, t.digitStart));
37836
- out.push(String(mapped));
37837
- out.push(sql.slice(t.digitEnd, t.appEnd));
37838
- cursor = t.fullEnd;
37786
+ return config2;
37787
+ }
37788
+ function createAppResolutionContext(config2, defaultProfile) {
37789
+ const profiles = Object.fromEntries(
37790
+ Object.entries(config2.profiles ?? {}).map(([name, profile2]) => [
37791
+ name,
37792
+ {
37793
+ logicalApps: profile2.logicalApps === void 0 ? void 0 : { ...profile2.logicalApps },
37794
+ allowPhysicalAppRefs: profile2.allowPhysicalAppRefs
37795
+ }
37796
+ ])
37797
+ );
37798
+ const implicitDefaultProfile = {};
37799
+ function requireProfile(profileName) {
37800
+ const profile2 = profiles[profileName];
37801
+ if (!profile2 && profileName === defaultProfile) return implicitDefaultProfile;
37802
+ if (!profile2) throw argumentError(`profile "${profileName}" is not defined.`);
37803
+ return profile2;
37839
37804
  }
37840
- out.push(sql.slice(cursor));
37841
37805
  return {
37842
- normalizedSql: out.join(""),
37843
- hasProfileSyntax,
37844
- appBindingByMappedApp
37806
+ resolveLogicalApp(name, profile2) {
37807
+ if (!LOGICAL_APP_NAME_RE.test(name)) {
37808
+ throw argumentError(`logical app name "${name}" must match [A-Z][A-Z0-9_]{0,63}.`);
37809
+ }
37810
+ const profileName = profile2 || defaultProfile;
37811
+ const logicalName = name.toUpperCase();
37812
+ const appId = requireProfile(profileName).logicalApps?.[logicalName];
37813
+ if (appId === void 0) {
37814
+ throw argumentError(`logical app LAPP_${logicalName}@${profileName} is not defined.`);
37815
+ }
37816
+ return appId;
37817
+ },
37818
+ assertPhysicalAppAllowed(profile2) {
37819
+ const profileName = profile2 || defaultProfile;
37820
+ if (!profiles[profileName]) return;
37821
+ if (requireProfile(profileName).allowPhysicalAppRefs === false) {
37822
+ throw argumentError(
37823
+ `physical app references are not allowed for profile "${profileName}"; use LAPP_<NAME>.`
37824
+ );
37825
+ }
37826
+ }
37845
37827
  };
37846
37828
  }
37847
- function buildCacheContext(defaultProfile, appBindingByMappedApp) {
37848
- if (appBindingByMappedApp.size === 0) return `default:${defaultProfile.toLowerCase()}`;
37849
- const pairs = [...appBindingByMappedApp.entries()].sort((a, b) => a[0] - b[0]).map(([mappedAppId, b]) => `M${mappedAppId}=APP${b.appId}@${b.profile}`);
37850
- return `apps:${pairs.join(",")}`;
37851
- }
37852
-
37853
- // src/node/config.ts
37854
- var import_fs = require("fs");
37855
37829
  function loadKsqlConfig(configPath) {
37856
37830
  const raw = (0, import_fs.readFileSync)(configPath, "utf-8");
37857
- return JSON.parse(raw);
37831
+ return validateKsqlConfig(JSON.parse(raw));
37858
37832
  }
37859
37833
  function loadOptionalKsqlConfig(configPath) {
37860
37834
  if (!(0, import_fs.existsSync)(configPath)) return {};
@@ -38051,11 +38025,19 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
38051
38025
  );
38052
38026
  }
38053
38027
  }
38054
- const res = await fetch(url2, {
38055
- ...init,
38056
- headers,
38057
- signal: AbortSignal.timeout(timeoutMs)
38058
- });
38028
+ const controller = new AbortController();
38029
+ const timeout2 = setTimeout(() => controller.abort(), timeoutMs);
38030
+ timeout2.unref?.();
38031
+ let res;
38032
+ try {
38033
+ res = await fetch(url2, {
38034
+ ...init,
38035
+ headers,
38036
+ signal: controller.signal
38037
+ });
38038
+ } finally {
38039
+ clearTimeout(timeout2);
38040
+ }
38059
38041
  if (!res.ok) {
38060
38042
  const bodyText = await res.text();
38061
38043
  if (tokenResolver.debug) {
@@ -38223,17 +38205,376 @@ function detectSortKind(fieldType, calcFormat) {
38223
38205
  return void 0;
38224
38206
  }
38225
38207
 
38208
+ // src/node/appProfiles.ts
38209
+ function parseTokenMap(raw) {
38210
+ const out = {};
38211
+ if (!raw.trim()) return out;
38212
+ const pairs = raw.split(",");
38213
+ for (const pair of pairs) {
38214
+ const idx = pair.indexOf("=");
38215
+ if (idx <= 0) throw new Error("ArgumentError: --token-map must be APPxxx=token pairs.");
38216
+ const key = normalizeAppKey(pair.slice(0, idx).trim());
38217
+ const value = pair.slice(idx + 1).trim();
38218
+ if (!value) throw new Error(`ArgumentError: token is empty for ${key}.`);
38219
+ out[key] = value;
38220
+ }
38221
+ return out;
38222
+ }
38223
+ function normalizeAppKey(v) {
38224
+ const m1 = v.match(/^APP(\d+)$/i);
38225
+ if (m1) return `APP${m1[1]}`;
38226
+ const m2 = v.match(/^(\d+)$/);
38227
+ if (m2) return `APP${m2[1]}`;
38228
+ throw new Error(`ArgumentError: invalid app key "${v}"`);
38229
+ }
38230
+ function extractAppIds(sql) {
38231
+ const out = /* @__PURE__ */ new Set();
38232
+ for (const t of collectAppProfileTokens(sql)) {
38233
+ if (t.source === "physical") out.add(t.appId);
38234
+ }
38235
+ return [...out];
38236
+ }
38237
+ function isSqlIdentContinue(ch) {
38238
+ if (!ch) return false;
38239
+ const cp = ch.codePointAt(0);
38240
+ return cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp >= 48 && cp <= 57 || cp === 95 || cp === 36 || cp >= 12352 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65281 && cp <= 65376;
38241
+ }
38242
+ function isProfileNameChar(ch) {
38243
+ if (!ch) return false;
38244
+ const cp = ch.codePointAt(0);
38245
+ return cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp >= 48 && cp <= 57 || ch === "_" || ch === "-" || ch === "." || ch === "$";
38246
+ }
38247
+ function isAsciiLogicalNameStart(ch) {
38248
+ return /^[A-Za-z]$/.test(ch);
38249
+ }
38250
+ function isAsciiLogicalNameContinue(ch) {
38251
+ return /^[A-Za-z0-9_]$/.test(ch);
38252
+ }
38253
+ function tryParseAppProfileToken(sql, start) {
38254
+ const prev = start > 0 ? sql[start - 1] : "";
38255
+ if (isSqlIdentContinue(prev)) return null;
38256
+ let source;
38257
+ let appId;
38258
+ let logicalName;
38259
+ let referenceValueStart;
38260
+ let referenceValueEnd;
38261
+ let i;
38262
+ if (sql.slice(start, start + 5).toUpperCase() === "LAPP_") {
38263
+ source = "logical";
38264
+ i = start + 5;
38265
+ referenceValueStart = i;
38266
+ if (!isAsciiLogicalNameStart(sql[i] ?? "")) return null;
38267
+ i++;
38268
+ while (i < sql.length && isAsciiLogicalNameContinue(sql[i])) i++;
38269
+ referenceValueEnd = i;
38270
+ if (referenceValueEnd - referenceValueStart > 64) return null;
38271
+ logicalName = sql.slice(referenceValueStart, referenceValueEnd).toUpperCase();
38272
+ } else if (sql.slice(start, start + 3).toUpperCase() === "APP") {
38273
+ source = "physical";
38274
+ i = start + 3;
38275
+ referenceValueStart = i;
38276
+ while (i < sql.length && /[0-9]/.test(sql[i])) i++;
38277
+ referenceValueEnd = i;
38278
+ if (referenceValueEnd === referenceValueStart) return null;
38279
+ appId = Number(sql.slice(referenceValueStart, referenceValueEnd));
38280
+ } else {
38281
+ return null;
38282
+ }
38283
+ if (sql[i] === "$") {
38284
+ i++;
38285
+ const subStart = i;
38286
+ while (i < sql.length && isSqlIdentContinue(sql[i])) i++;
38287
+ if (i === subStart) return null;
38288
+ }
38289
+ const appEnd = i;
38290
+ let profile2 = null;
38291
+ if (sql[i] === "@") {
38292
+ i++;
38293
+ const pStart = i;
38294
+ while (i < sql.length && isProfileNameChar(sql[i])) i++;
38295
+ if (i === pStart) return null;
38296
+ profile2 = sql.slice(pStart, i);
38297
+ }
38298
+ const next = i < sql.length ? sql[i] : "";
38299
+ if (isSqlIdentContinue(next)) return null;
38300
+ const common = {
38301
+ profile: profile2,
38302
+ start,
38303
+ referenceValueStart,
38304
+ referenceValueEnd,
38305
+ appEnd,
38306
+ fullEnd: i
38307
+ };
38308
+ return source === "physical" ? { ...common, source, appId } : { ...common, source, logicalName };
38309
+ }
38310
+ function collectAppProfileTokens(sql) {
38311
+ const tokens = [];
38312
+ let i = 0;
38313
+ while (i < sql.length) {
38314
+ const ch = sql[i];
38315
+ if (ch === "'") {
38316
+ i++;
38317
+ while (i < sql.length) {
38318
+ if (sql[i] === "'") {
38319
+ i++;
38320
+ if (i < sql.length && sql[i] === "'") {
38321
+ i++;
38322
+ continue;
38323
+ }
38324
+ break;
38325
+ }
38326
+ i++;
38327
+ }
38328
+ continue;
38329
+ }
38330
+ if (ch === "`") {
38331
+ i++;
38332
+ while (i < sql.length && sql[i] !== "`") i++;
38333
+ if (i < sql.length) i++;
38334
+ continue;
38335
+ }
38336
+ if (ch === "-" && sql[i + 1] === "-") {
38337
+ i += 2;
38338
+ while (i < sql.length && sql[i] !== "\n") i++;
38339
+ continue;
38340
+ }
38341
+ if (ch === "/" && sql[i + 1] === "*") {
38342
+ i += 2;
38343
+ while (i < sql.length) {
38344
+ if (sql[i] === "*" && sql[i + 1] === "/") {
38345
+ i += 2;
38346
+ break;
38347
+ }
38348
+ i++;
38349
+ }
38350
+ continue;
38351
+ }
38352
+ const parsed = tryParseAppProfileToken(sql, i);
38353
+ if (!parsed) {
38354
+ i++;
38355
+ continue;
38356
+ }
38357
+ tokens.push(parsed);
38358
+ i = parsed.fullEnd;
38359
+ }
38360
+ return tokens;
38361
+ }
38362
+ function nextVirtualAppId(used) {
38363
+ let id = 9e8;
38364
+ while (used.has(id)) id++;
38365
+ used.add(id);
38366
+ return id;
38367
+ }
38368
+ function normalizeSqlAppProfiles(sql, defaultProfile = "dev", resolutionContext) {
38369
+ const tokens = collectAppProfileTokens(sql);
38370
+ const hasProfileSyntax = tokens.some((t) => t.profile !== null);
38371
+ const profilesByApp = /* @__PURE__ */ new Map();
38372
+ const normalizedProfile = (profile2) => profile2 ?? defaultProfile;
38373
+ for (const t of tokens) {
38374
+ if (t.source !== "physical") continue;
38375
+ const p = normalizedProfile(t.profile);
38376
+ let set2 = profilesByApp.get(t.appId);
38377
+ if (!set2) {
38378
+ set2 = /* @__PURE__ */ new Set();
38379
+ profilesByApp.set(t.appId, set2);
38380
+ }
38381
+ set2.add(p.toLowerCase());
38382
+ }
38383
+ const usedAppIds = new Set(
38384
+ tokens.filter((t) => t.source === "physical").map((t) => t.appId)
38385
+ );
38386
+ const resolvedLogicalApps = /* @__PURE__ */ new Map();
38387
+ for (const t of tokens) {
38388
+ if (t.source !== "logical") continue;
38389
+ const pLower = normalizedProfile(t.profile).toLowerCase();
38390
+ const logicalKey = `logical:${t.logicalName}@${pLower}`;
38391
+ if (resolvedLogicalApps.has(logicalKey)) continue;
38392
+ if (!resolutionContext) {
38393
+ throw new Error(
38394
+ `ArgumentError: logical app LAPP_${t.logicalName}@${pLower} requires logicalApps configuration.`
38395
+ );
38396
+ }
38397
+ const resolvedAppId = resolutionContext.resolveLogicalApp(t.logicalName, pLower);
38398
+ resolvedLogicalApps.set(logicalKey, resolvedAppId);
38399
+ usedAppIds.add(resolvedAppId);
38400
+ }
38401
+ const pairToMapped = /* @__PURE__ */ new Map();
38402
+ const appBindingByMappedApp = /* @__PURE__ */ new Map();
38403
+ for (const [appId, pSet] of profilesByApp.entries()) {
38404
+ const profiles = [...pSet].sort();
38405
+ if (profiles.length <= 1) continue;
38406
+ for (const pLower of profiles) {
38407
+ const mapped = nextVirtualAppId(usedAppIds);
38408
+ pairToMapped.set(`physical:${appId}@${pLower}`, mapped);
38409
+ appBindingByMappedApp.set(mapped, {
38410
+ source: "physical",
38411
+ mappedAppId: mapped,
38412
+ appId,
38413
+ profile: pLower
38414
+ });
38415
+ }
38416
+ }
38417
+ const out = [];
38418
+ const rewriteSegments = [];
38419
+ let normalizedLength = 0;
38420
+ let cursor = 0;
38421
+ const appendSegment = (text, sourceStart, sourceEnd, bindingMappedAppId) => {
38422
+ if (!text && sourceStart === sourceEnd) return;
38423
+ const normalizedStart = normalizedLength;
38424
+ out.push(text);
38425
+ normalizedLength += text.length;
38426
+ rewriteSegments.push({
38427
+ normalizedStart,
38428
+ normalizedEnd: normalizedLength,
38429
+ sourceStart,
38430
+ sourceEnd,
38431
+ ...bindingMappedAppId === void 0 ? {} : { bindingMappedAppId }
38432
+ });
38433
+ };
38434
+ for (const t of tokens) {
38435
+ const p = normalizedProfile(t.profile);
38436
+ const pLower = p.toLowerCase();
38437
+ let binding;
38438
+ if (t.source === "physical") {
38439
+ const mapped = pairToMapped.get(`physical:${t.appId}@${pLower}`) ?? t.appId;
38440
+ binding = {
38441
+ source: "physical",
38442
+ mappedAppId: mapped,
38443
+ appId: t.appId,
38444
+ profile: pLower
38445
+ };
38446
+ } else {
38447
+ const logicalKey = `logical:${t.logicalName}@${pLower}`;
38448
+ let mapped = pairToMapped.get(logicalKey);
38449
+ if (mapped === void 0) {
38450
+ mapped = nextVirtualAppId(usedAppIds);
38451
+ pairToMapped.set(logicalKey, mapped);
38452
+ }
38453
+ binding = {
38454
+ source: "logical",
38455
+ logicalName: t.logicalName,
38456
+ mappedAppId: mapped,
38457
+ appId: resolvedLogicalApps.get(logicalKey),
38458
+ profile: pLower
38459
+ };
38460
+ }
38461
+ appBindingByMappedApp.set(binding.mappedAppId, binding);
38462
+ appendSegment(sql.slice(cursor, t.start), cursor, t.start);
38463
+ const subtableSuffix = sql.slice(t.referenceValueEnd, t.appEnd);
38464
+ const normalizedReference = t.source === "physical" ? `${sql.slice(t.start, t.referenceValueStart)}${binding.mappedAppId}${subtableSuffix}` : `APP${binding.mappedAppId}${subtableSuffix}`;
38465
+ appendSegment(
38466
+ normalizedReference,
38467
+ t.start,
38468
+ t.fullEnd,
38469
+ binding.mappedAppId
38470
+ );
38471
+ cursor = t.fullEnd;
38472
+ }
38473
+ appendSegment(sql.slice(cursor), cursor, sql.length);
38474
+ return {
38475
+ normalizedSql: out.join(""),
38476
+ hasProfileSyntax,
38477
+ appBindingByMappedApp,
38478
+ rewriteSegments
38479
+ };
38480
+ }
38481
+ function buildCacheContext(defaultProfile, appBindingByMappedApp) {
38482
+ if (appBindingByMappedApp.size === 0) return `default:${defaultProfile.toLowerCase()}`;
38483
+ const pairs = [...appBindingByMappedApp.entries()].sort((a, b) => a[0] - b[0]).map(([mappedAppId, b]) => b.source === "logical" ? `M${mappedAppId}=logical:${b.logicalName}:APP${b.appId}@${b.profile}` : `M${mappedAppId}=physical:APP${b.appId}@${b.profile}`);
38484
+ return `apps:${pairs.join(",")}`;
38485
+ }
38486
+
38226
38487
  // src/node/runtime.ts
38488
+ var privateConfigSnapshots = /* @__PURE__ */ new WeakMap();
38489
+ function cloneConfigSnapshot(config2) {
38490
+ return JSON.parse(JSON.stringify(config2));
38491
+ }
38492
+ function toConfigView(config2) {
38493
+ const profiles = Object.fromEntries(Object.entries(config2.profiles ?? {}).map(([name, p]) => [name, {
38494
+ baseUrl: p.baseUrl,
38495
+ logicalApps: p.logicalApps === void 0 ? void 0 : Object.freeze({ ...p.logicalApps }),
38496
+ allowPhysicalAppRefs: p.allowPhysicalAppRefs,
38497
+ passwordEnv: p.passwordEnv,
38498
+ tokenMapSources: p.tokenMap === void 0 ? void 0 : Object.freeze(Object.fromEntries(
38499
+ Object.entries(p.tokenMap).map(([key, value]) => [key, String(value).startsWith("env:") ? String(value).slice(4) : "inline"])
38500
+ ))
38501
+ }]));
38502
+ return Object.freeze({ defaultProfile: config2.defaultProfile, profiles: Object.freeze(profiles) });
38503
+ }
38504
+ function resolveSqlContext(serverOptions, sql, inputProfile) {
38505
+ const configPath = serverOptions.configPath ?? envString("KSQL_CONFIG") ?? "./ksql.config.json";
38506
+ const config2 = cloneConfigSnapshot(loadOptionalKsqlConfig(configPath));
38507
+ const profileName = resolveDefaultProfile(config2, serverOptions, inputProfile);
38508
+ const resolutionContext = createAppResolutionContext(config2, profileName);
38509
+ const normalized = normalizeSqlAppProfiles(sql, profileName, resolutionContext);
38510
+ const bindings = normalized.appBindingByMappedApp;
38511
+ for (const binding of bindings.values()) {
38512
+ if (binding.source === "physical") resolutionContext.assertPhysicalAppAllowed(binding.profile);
38513
+ }
38514
+ const context = {
38515
+ normalizedSql: normalized.normalizedSql,
38516
+ bindings,
38517
+ cacheContext: buildCacheContext(profileName, bindings),
38518
+ profileName,
38519
+ rewriteSegments: Object.freeze(normalized.rewriteSegments.map((segment) => Object.freeze({ ...segment }))),
38520
+ hasProfileSyntax: normalized.hasProfileSyntax,
38521
+ configSnapshot: toConfigView(config2),
38522
+ logicalBindingLabels: new Map(
38523
+ [...bindings.values()].filter((b) => b.source === "logical").map((b) => [b.mappedAppId, `LAPP_${b.logicalName}@${b.profile}`])
38524
+ )
38525
+ };
38526
+ Object.freeze(context);
38527
+ privateConfigSnapshots.set(context, config2);
38528
+ return context;
38529
+ }
38530
+ function resolveTokenByMappedApp(args) {
38531
+ const tokenByMappedApp = /* @__PURE__ */ new Map();
38532
+ const tokenByPhysicalApp = /* @__PURE__ */ new Map();
38533
+ const missing = [];
38534
+ for (const mappedAppId of args.mappedAppIds) {
38535
+ const binding = args.bindings.get(mappedAppId);
38536
+ if (!binding && args.logicalBindingLabels.has(mappedAppId)) {
38537
+ throw new Error(`InternalError: binding is missing for logical app ${args.logicalBindingLabels.get(mappedAppId)}.`);
38538
+ }
38539
+ const appId = binding?.appId ?? mappedAppId;
38540
+ const profile2 = binding?.profile ?? args.profileName;
38541
+ const fromMap = args.effectiveTokenMap[`APP${appId}`];
38542
+ if (fromMap) {
38543
+ const token = resolveTokenValue(fromMap);
38544
+ tokenByMappedApp.set(mappedAppId, token);
38545
+ tokenByPhysicalApp.set(appId, token);
38546
+ continue;
38547
+ }
38548
+ if (binding?.source !== "logical" && args.mappedAppIds.length === 1 && args.singleToken) {
38549
+ tokenByMappedApp.set(mappedAppId, args.singleToken);
38550
+ tokenByPhysicalApp.set(appId, args.singleToken);
38551
+ continue;
38552
+ }
38553
+ missing.push(binding?.source === "logical" ? `LAPP_${binding.logicalName} (APP${appId})@${profile2}` : `APP${appId}@${profile2}`);
38554
+ }
38555
+ return { tokenByMappedApp, tokenByPhysicalApp, missing };
38556
+ }
38557
+ function resolveRuntimeBinding(context, mappedAppId) {
38558
+ const binding = context.bindings.get(mappedAppId);
38559
+ if (binding) return binding;
38560
+ const logicalLabel = context.logicalBindingLabels.get(mappedAppId);
38561
+ if (logicalLabel) {
38562
+ throw new Error(`InternalError: binding is missing for logical app ${logicalLabel}.`);
38563
+ }
38564
+ return { appId: mappedAppId, profile: context.profileName.toLowerCase() };
38565
+ }
38227
38566
  function resolveDefaultProfile(config2, serverOptions, inputProfile) {
38228
38567
  return inputProfile ?? serverOptions.profile ?? envString("KSQL_PROFILE") ?? config2.defaultProfile ?? "dev";
38229
38568
  }
38230
38569
  async function createKsqlRuntime(serverOptions, input) {
38231
- const configPath = serverOptions.configPath ?? envString("KSQL_CONFIG") ?? "./ksql.config.json";
38232
- const config2 = loadOptionalKsqlConfig(configPath);
38233
- const profileName = resolveDefaultProfile(config2, serverOptions, input.profile);
38570
+ const sqlContext = input.sqlContext ?? resolveSqlContext(serverOptions, input.sql, input.profile);
38571
+ const config2 = privateConfigSnapshots.get(sqlContext);
38572
+ if (!config2) {
38573
+ throw new Error("InternalError: private config snapshot is missing for resolved SQL context.");
38574
+ }
38575
+ const profileName = sqlContext.profileName;
38234
38576
  const profile2 = config2.profiles?.[profileName] ?? {};
38235
- const normalized = normalizeSqlAppProfiles(input.sql, profileName);
38236
- const sql = normalized.normalizedSql;
38577
+ const sql = sqlContext.normalizedSql;
38237
38578
  const maxRecords2 = input.maxRecords ?? envInt("KSQL_MAX_RECORDS") ?? profile2.query?.maxRecords ?? 500;
38238
38579
  const fetchParallel2 = input.fetchParallel ?? envInt("KSQL_FETCH_PARALLEL") ?? profile2.query?.fetchParallel ?? 3;
38239
38580
  if (!Number.isInteger(fetchParallel2) || fetchParallel2 < 1 || fetchParallel2 > 10) {
@@ -38249,11 +38590,12 @@ async function createKsqlRuntime(serverOptions, input) {
38249
38590
  for (const appId of appIds) {
38250
38591
  appProfileByApp.set(
38251
38592
  appId,
38252
- normalized.appBindingByMappedApp.get(appId)?.profile ?? profileName.toLowerCase()
38593
+ sqlContext.bindings.get(appId)?.profile ?? profileName.toLowerCase()
38253
38594
  );
38254
38595
  }
38255
38596
  const usedProfiles = /* @__PURE__ */ new Set([...appProfileByApp.values(), profileName]);
38256
38597
  const profileClientMap = /* @__PURE__ */ new Map();
38598
+ const allTokenByMappedApp = /* @__PURE__ */ new Map();
38257
38599
  const missingAppProfiles = [];
38258
38600
  const tokenMapEnv = envString("KSQL_TOKEN_MAP");
38259
38601
  const mapFromEnv = tokenMapEnv ? parseTokenMap(tokenMapEnv) : {};
@@ -38291,21 +38633,19 @@ async function createKsqlRuntime(serverOptions, input) {
38291
38633
  );
38292
38634
  const effectiveTokenMap = { ...mapFromConfig, ...mapFromEnv };
38293
38635
  const assignedAppIds = appIds.filter((appId) => appProfileByApp.get(appId) === pName);
38294
- const tokenByApp = /* @__PURE__ */ new Map();
38295
- for (const mappedAppId of assignedAppIds) {
38296
- const realAppId = normalized.appBindingByMappedApp.get(mappedAppId)?.appId ?? mappedAppId;
38297
- const key = `APP${realAppId}`;
38298
- const fromMap = effectiveTokenMap[key];
38299
- if (fromMap) {
38300
- tokenByApp.set(mappedAppId, resolveTokenValue(fromMap));
38301
- continue;
38302
- }
38303
- if (assignedAppIds.length === 1 && singleToken) {
38304
- tokenByApp.set(mappedAppId, singleToken);
38305
- continue;
38306
- }
38307
- missingAppProfiles.push(`APP${realAppId}@${pName}`);
38636
+ const resolvedTokens = resolveTokenByMappedApp({
38637
+ mappedAppIds: assignedAppIds,
38638
+ profileName: pName,
38639
+ bindings: sqlContext.bindings,
38640
+ logicalBindingLabels: sqlContext.logicalBindingLabels,
38641
+ effectiveTokenMap,
38642
+ singleToken
38643
+ });
38644
+ const tokenByApp = resolvedTokens.tokenByPhysicalApp;
38645
+ for (const [mappedAppId, token] of resolvedTokens.tokenByMappedApp) {
38646
+ allTokenByMappedApp.set(mappedAppId, token);
38308
38647
  }
38648
+ missingAppProfiles.push(...resolvedTokens.missing);
38309
38649
  if (assignedAppIds.length === 0 && singleToken) {
38310
38650
  tokenByApp.set(0, singleToken);
38311
38651
  }
@@ -38328,38 +38668,43 @@ async function createKsqlRuntime(serverOptions, input) {
38328
38668
  if (missingAppProfiles.length > 0) {
38329
38669
  throw new Error(`AuthError: token is missing for ${missingAppProfiles.join(", ")}.`);
38330
38670
  }
38331
- const defaultClient = profileClientMap.get(profileName);
38671
+ const runtimeContext = {
38672
+ sqlContext,
38673
+ tokenByMappedApp: allTokenByMappedApp,
38674
+ clientsByProfile: profileClientMap
38675
+ };
38676
+ const defaultClient = runtimeContext.clientsByProfile.get(profileName);
38332
38677
  if (!defaultClient) {
38333
38678
  throw new Error(`AuthError: profile client is not resolved for "${profileName}".`);
38334
38679
  }
38335
38680
  const routedClient = {
38336
38681
  getRecords: (params) => {
38337
- const binding = normalized.appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
38338
- const routed = profileClientMap.get(binding.profile);
38682
+ const binding = resolveRuntimeBinding(runtimeContext.sqlContext, params.app);
38683
+ const routed = runtimeContext.clientsByProfile.get(binding.profile);
38339
38684
  if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${params.app}.`);
38340
38685
  return routed.getRecords({ ...params, app: binding.appId });
38341
38686
  },
38342
38687
  postRecords: (params) => {
38343
- const binding = normalized.appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
38344
- const routed = profileClientMap.get(binding.profile);
38688
+ const binding = resolveRuntimeBinding(runtimeContext.sqlContext, params.app);
38689
+ const routed = runtimeContext.clientsByProfile.get(binding.profile);
38345
38690
  if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${params.app}.`);
38346
38691
  return routed.postRecords({ ...params, app: binding.appId });
38347
38692
  },
38348
38693
  putRecords: (params) => {
38349
- const binding = normalized.appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
38350
- const routed = profileClientMap.get(binding.profile);
38694
+ const binding = resolveRuntimeBinding(runtimeContext.sqlContext, params.app);
38695
+ const routed = runtimeContext.clientsByProfile.get(binding.profile);
38351
38696
  if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${params.app}.`);
38352
38697
  return routed.putRecords({ ...params, app: binding.appId });
38353
38698
  },
38354
38699
  deleteRecords: (params) => {
38355
- const binding = normalized.appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
38356
- const routed = profileClientMap.get(binding.profile);
38700
+ const binding = resolveRuntimeBinding(runtimeContext.sqlContext, params.app);
38701
+ const routed = runtimeContext.clientsByProfile.get(binding.profile);
38357
38702
  if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${params.app}.`);
38358
38703
  return routed.deleteRecords({ ...params, app: binding.appId });
38359
38704
  },
38360
38705
  getFields: (appId) => {
38361
- const binding = normalized.appBindingByMappedApp.get(appId) ?? { appId, profile: profileName.toLowerCase() };
38362
- const routed = profileClientMap.get(binding.profile);
38706
+ const binding = resolveRuntimeBinding(runtimeContext.sqlContext, appId);
38707
+ const routed = runtimeContext.clientsByProfile.get(binding.profile);
38363
38708
  if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${appId}.`);
38364
38709
  return routed.getFields(binding.appId);
38365
38710
  },
@@ -38378,7 +38723,7 @@ async function createKsqlRuntime(serverOptions, input) {
38378
38723
  sql,
38379
38724
  profileName,
38380
38725
  client: gatedClient,
38381
- cacheContext: buildCacheContext(profileName, normalized.appBindingByMappedApp),
38726
+ cacheContext: sqlContext.cacheContext,
38382
38727
  maxRecords: maxRecords2,
38383
38728
  fetchParallel: fetchParallel2,
38384
38729
  onLimit: onLimit2,
@@ -38590,11 +38935,6 @@ function noOpClient() {
38590
38935
  getFields: fail
38591
38936
  };
38592
38937
  }
38593
- function getToolProfile(serverOptions, inputProfile) {
38594
- const configPath = getServerConfigPath(serverOptions);
38595
- const config2 = loadOptionalKsqlConfig(configPath);
38596
- return resolveDefaultProfile(config2, serverOptions, inputProfile);
38597
- }
38598
38938
  function getServerConfigPath(serverOptions) {
38599
38939
  return serverOptions.configPath ?? envString("KSQL_CONFIG") ?? "./ksql.config.json";
38600
38940
  }
@@ -38607,16 +38947,34 @@ function getSavedQueryCatalogPath(serverOptions) {
38607
38947
  });
38608
38948
  }
38609
38949
  function normalizeSqlForTool(serverOptions, sql, inputProfile) {
38610
- const profileName = getToolProfile(serverOptions, inputProfile);
38611
- const normalized = normalizeSqlAppProfiles(sql, profileName);
38950
+ const sqlContext = resolveSqlContext(serverOptions, sql, inputProfile);
38612
38951
  return {
38613
- profileName,
38614
- normalizedSql: normalized.normalizedSql,
38615
- hasProfileSyntax: normalized.hasProfileSyntax,
38616
- appBindingByMappedApp: normalized.appBindingByMappedApp,
38617
- cacheContext: buildCacheContext(profileName, normalized.appBindingByMappedApp)
38952
+ profileName: sqlContext.profileName,
38953
+ normalizedSql: sqlContext.normalizedSql,
38954
+ hasProfileSyntax: sqlContext.hasProfileSyntax,
38955
+ appBindingByMappedApp: sqlContext.bindings,
38956
+ cacheContext: sqlContext.cacheContext,
38957
+ sqlContext,
38958
+ sourceSql: sql
38959
+ };
38960
+ }
38961
+ function toValidationBinding(mappedAppId, binding) {
38962
+ return binding.source === "logical" ? {
38963
+ source: binding.source,
38964
+ logicalName: binding.logicalName,
38965
+ mappedAppId,
38966
+ appId: binding.appId,
38967
+ profile: binding.profile
38968
+ } : {
38969
+ source: binding.source,
38970
+ mappedAppId,
38971
+ appId: binding.appId,
38972
+ profile: binding.profile
38618
38973
  };
38619
38974
  }
38975
+ function toExplainBindings(bindings) {
38976
+ return [...bindings.values()].map((binding) => binding.source === "logical" ? { source: binding.source, logicalName: binding.logicalName, appId: binding.appId, profile: binding.profile } : { source: binding.source, appId: binding.appId, profile: binding.profile });
38977
+ }
38620
38978
  function explainSql(sql) {
38621
38979
  return /^\s*EXPLAIN\b/i.test(sql) ? sql : `EXPLAIN ${sql}`;
38622
38980
  }
@@ -38740,15 +39098,17 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38740
39098
  const createRuntime = deps.createRuntime ?? createKsqlRuntime;
38741
39099
  const executeSql = deps.executeSql ?? execute;
38742
39100
  const executeBatchSql = deps.executeBatchSql ?? executeBatch;
39101
+ const validationContexts = /* @__PURE__ */ new WeakMap();
38743
39102
  async function validate(input) {
38744
39103
  const normalized = normalizeSqlForTool(serverOptions, input.sql, input.profile);
38745
- const statements = parseSqlStatements(normalized.normalizedSql);
38746
- const analysis = analyzeBatch(statements);
38747
- const appBindings = [...normalized.appBindingByMappedApp.entries()].map(([mappedAppId, binding]) => ({
38748
- mappedAppId,
38749
- appId: binding.appId,
38750
- profile: binding.profile
38751
- }));
39104
+ let analysis;
39105
+ try {
39106
+ const statements = parseSqlStatements(normalized.normalizedSql);
39107
+ analysis = analyzeBatch(statements);
39108
+ } catch (err) {
39109
+ throw restoreSqlContextError(err, normalized.sourceSql, normalized.sqlContext);
39110
+ }
39111
+ const appBindings = [...normalized.appBindingByMappedApp.entries()].map(([mappedAppId, binding]) => toValidationBinding(mappedAppId, binding));
38752
39112
  const statementValidations = analysis.statements.map((s2) => ({
38753
39113
  index: s2.index,
38754
39114
  statementType: s2.statementType,
@@ -38778,10 +39138,12 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38778
39138
  appBindings
38779
39139
  };
38780
39140
  if (analysis.statementCount > 1) {
38781
- return { ...common, batch: true };
39141
+ const result2 = { ...common, batch: true };
39142
+ validationContexts.set(result2, normalized.sqlContext);
39143
+ return result2;
38782
39144
  }
38783
39145
  const s = statementValidations[0];
38784
- return {
39146
+ const result = {
38785
39147
  ...common,
38786
39148
  batch: false,
38787
39149
  statementType: s.statementType,
@@ -38791,17 +39153,26 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38791
39153
  insertValuesCount: s.insertValuesCount,
38792
39154
  appIds: s.appIds
38793
39155
  };
39156
+ validationContexts.set(result, normalized.sqlContext);
39157
+ return result;
38794
39158
  }
38795
39159
  async function explain(input) {
38796
39160
  const normalized = normalizeSqlForTool(serverOptions, input.sql, input.profile);
38797
- const statements = parseSqlStatements(normalized.normalizedSql);
39161
+ const appBindings = toExplainBindings(normalized.appBindingByMappedApp);
39162
+ let statements;
39163
+ try {
39164
+ statements = parseSqlStatements(normalized.normalizedSql);
39165
+ } catch (err) {
39166
+ throw restoreSqlContextError(err, normalized.sourceSql, normalized.sqlContext);
39167
+ }
38798
39168
  if (statements.length > 1) {
38799
39169
  const plans = buildBatchExplainPlans(normalized.normalizedSql);
38800
39170
  return {
38801
39171
  ok: true,
38802
39172
  batch: true,
38803
39173
  statementCount: plans.statementCount,
38804
- statements: plans.statements
39174
+ statements: restoreSqlDiagnosticValue(plans.statements, normalized.appBindingByMappedApp),
39175
+ appBindings
38805
39176
  };
38806
39177
  }
38807
39178
  const result = await executeSql(explainSql(normalized.normalizedSql), noOpClient(), {
@@ -38810,16 +39181,20 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38810
39181
  if (result.type !== "SELECT") {
38811
39182
  throw new Error(`ArgumentError: EXPLAIN returned unexpected result type ${result.type}.`);
38812
39183
  }
38813
- return toSelectPayload(result);
39184
+ return restoreSqlDiagnosticValue(
39185
+ { ...toSelectPayload(result), appBindings },
39186
+ normalized.appBindingByMappedApp
39187
+ );
38814
39188
  }
38815
- async function query(input) {
38816
- const validation = await validate(input);
39189
+ async function query(input, validated) {
39190
+ const validation = validated ?? await validate(input);
38817
39191
  if (validation.batch) {
38818
39192
  if (validation.containsDml) {
38819
39193
  throw new Error("ArgumentError: batch contains DML statements. Use ksql_mutate.");
38820
39194
  }
38821
39195
  const runtime2 = await createRuntime(serverOptions, {
38822
39196
  sql: input.sql,
39197
+ sqlContext: validationContexts.get(validation),
38823
39198
  profile: input.profile,
38824
39199
  maxRecords: input.maxRecords,
38825
39200
  fetchParallel: input.fetchParallel,
@@ -38862,6 +39237,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38862
39237
  }
38863
39238
  const runtime = await createRuntime(serverOptions, {
38864
39239
  sql: input.sql,
39240
+ sqlContext: validationContexts.get(validation),
38865
39241
  profile: input.profile,
38866
39242
  maxRecords: input.maxRecords,
38867
39243
  fetchParallel: input.fetchParallel,
@@ -38907,6 +39283,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38907
39283
  const selectBasedDml = containsSelectBasedDml(validation.statements);
38908
39284
  const runtime = await createRuntime(serverOptions, {
38909
39285
  sql: input.sql,
39286
+ sqlContext: validationContexts.get(validation),
38910
39287
  profile: input.profile,
38911
39288
  // SELECT-based DML を含む場合は dmlMaxRows で読み取りを絞らない(案A。
38912
39289
  // resolveMutateRuntimeMaxRecords の doc コメント参照)
@@ -38951,9 +39328,9 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38951
39328
  }
38952
39329
  return { ...payload };
38953
39330
  }
38954
- async function mutate(input) {
39331
+ async function mutate(input, validated) {
38955
39332
  const dmlMaxRows = requireDmlApproval(input, "ksql_mutate");
38956
- const validation = await validate(input);
39333
+ const validation = validated ?? await validate(input);
38957
39334
  if (validation.batch) {
38958
39335
  return mutateBatch(input, validation, dmlMaxRows);
38959
39336
  }
@@ -38969,6 +39346,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38969
39346
  const selectBasedDml = containsSelectBasedDml(validation.statements);
38970
39347
  const runtime = await createRuntime(serverOptions, {
38971
39348
  sql: input.sql,
39349
+ sqlContext: validationContexts.get(validation),
38972
39350
  profile: input.profile,
38973
39351
  // SELECT-based DML は dmlMaxRows で読み取りを絞らない(案A。
38974
39352
  // resolveMutateRuntimeMaxRecords の doc コメント参照)
@@ -39089,7 +39467,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
39089
39467
  fetchParallel: input.fetchParallel,
39090
39468
  onLimit: input.onLimit,
39091
39469
  timeout: input.timeout
39092
- });
39470
+ }, validation);
39093
39471
  return {
39094
39472
  ok: true,
39095
39473
  name: saved.name,
@@ -39105,7 +39483,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
39105
39483
  dmlMaxRows,
39106
39484
  fetchParallel: input.fetchParallel,
39107
39485
  timeout: input.timeout
39108
- });
39486
+ }, validation);
39109
39487
  return {
39110
39488
  ok: true,
39111
39489
  name: saved.name,
@@ -39275,7 +39653,7 @@ Options:
39275
39653
  -h, --help Show help
39276
39654
  `);
39277
39655
  }
39278
- var SERVER_VERSION = true ? "1.12.1" : "0.0.0-dev";
39656
+ var SERVER_VERSION = true ? "1.13.1" : "0.0.0-dev";
39279
39657
  function createServer(args) {
39280
39658
  const server = new McpServer({
39281
39659
  name: "ksql-mcp",