@youtyan/code-viewer 0.2.3 → 0.2.4

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.
@@ -3884,6 +3884,9 @@ import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
3884
3884
  function dockerDatabasesCacheKey(serviceName, kind, cwd) {
3885
3885
  return `${serviceName}\x00${kind}\x00${cwd}`;
3886
3886
  }
3887
+ function dockerSchemasCacheKey(serviceName, kind, cwd, database) {
3888
+ return `${serviceName}\x00${kind}\x00${cwd}\x00${database}`;
3889
+ }
3887
3890
  function setDockerDatabasesCache(key, value, ttlMs, now = Date.now()) {
3888
3891
  const cachedValue = [...value];
3889
3892
  dockerDatabasesCache.set(key, {
@@ -3892,6 +3895,14 @@ function setDockerDatabasesCache(key, value, ttlMs, now = Date.now()) {
3892
3895
  });
3893
3896
  return [...cachedValue];
3894
3897
  }
3898
+ function setDockerSchemasCache(key, value, ttlMs, now = Date.now()) {
3899
+ const cachedValue = [...value];
3900
+ dockerSchemasCache.set(key, {
3901
+ value: cachedValue,
3902
+ expiresAt: now + ttlMs
3903
+ });
3904
+ return [...cachedValue];
3905
+ }
3895
3906
  function fallbackDockerDatabases(defaultDb) {
3896
3907
  return defaultDb ? [defaultDb] : [];
3897
3908
  }
@@ -4209,10 +4220,26 @@ function createDockerAdapter(config) {
4209
4220
  }
4210
4221
  const columnCache = new Map;
4211
4222
  const tableMetaCache = createTableMetaCache();
4223
+ function currentPostgresSchema() {
4224
+ return config.schema || "public";
4225
+ }
4226
+ function postgresSchemaLiteral() {
4227
+ return escapeSqlString(currentPostgresSchema());
4228
+ }
4229
+ function tableIdentifier(table) {
4230
+ if (config.kind === "postgresql") {
4231
+ return `${sanitizeIdentifier(currentPostgresSchema(), config.kind)}.${sanitizeIdentifier(table, config.kind)}`;
4232
+ }
4233
+ return sanitizeIdentifier(table, config.kind);
4234
+ }
4235
+ function postgresRegclassLiteral(table) {
4236
+ return escapeSqlString(`${sanitizeIdentifier(currentPostgresSchema(), "postgresql")}.${sanitizeIdentifier(table, "postgresql")}`);
4237
+ }
4212
4238
  function buildColumnsSql(table) {
4213
4239
  const tableLiteral = table.replace(/'/g, "''");
4214
4240
  if (config.kind === "postgresql") {
4215
- return `SELECT c.column_name, c.data_type, c.is_nullable, c.column_default, CASE WHEN pk.column_name IS NULL THEN 'NO' ELSE 'YES' END FROM information_schema.columns c LEFT JOIN (SELECT kcu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name WHERE tc.table_schema = 'public' AND tc.table_name = '${tableLiteral}' AND tc.constraint_type = 'PRIMARY KEY') pk ON pk.column_name = c.column_name WHERE c.table_schema = 'public' AND c.table_name = '${tableLiteral}' ORDER BY c.ordinal_position`;
4241
+ const schemaLiteral = postgresSchemaLiteral();
4242
+ return `SELECT c.column_name, c.data_type, c.is_nullable, c.column_default, CASE WHEN pk.column_name IS NULL THEN 'NO' ELSE 'YES' END FROM information_schema.columns c LEFT JOIN (SELECT kcu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name WHERE tc.table_schema = ${schemaLiteral} AND tc.table_name = '${tableLiteral}' AND tc.constraint_type = 'PRIMARY KEY') pk ON pk.column_name = c.column_name WHERE c.table_schema = ${schemaLiteral} AND c.table_name = '${tableLiteral}' ORDER BY c.ordinal_position`;
4216
4243
  }
4217
4244
  return `SELECT column_name, column_type, is_nullable, column_default, column_key FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = '${tableLiteral}' ORDER BY ordinal_position`;
4218
4245
  }
@@ -4250,37 +4277,8 @@ function createDockerAdapter(config) {
4250
4277
  return countResult.rows.length > 0 ? Number(countResult.rows[0][0]) || 0 : 0;
4251
4278
  }
4252
4279
  function fetchColumnsUncached(table) {
4253
- let sql;
4254
- if (config.kind === "postgresql") {
4255
- sql = `SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${table.replace(/'/g, "''")}' ORDER BY ordinal_position`;
4256
- } else {
4257
- sql = `SELECT column_name, column_type, is_nullable, column_default, column_key FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = '${table.replace(/'/g, "''")}' ORDER BY ordinal_position`;
4258
- }
4259
- const result = exec(sql);
4260
- if (config.kind === "postgresql") {
4261
- const pkSql = `SELECT a.attname FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = '${table.replace(/'/g, "''")}'::regclass AND i.indisprimary`;
4262
- let pkCols;
4263
- try {
4264
- const pkResult = exec(pkSql);
4265
- pkCols = new Set(pkResult.rows.map((r) => r[0]));
4266
- } catch {
4267
- pkCols = new Set;
4268
- }
4269
- return result.rows.map((row) => ({
4270
- name: row[0],
4271
- type: row[1],
4272
- nullable: row[2] === "YES",
4273
- primaryKey: pkCols.has(row[0]),
4274
- defaultValue: row[3] === "" ? null : row[3]
4275
- }));
4276
- }
4277
- return result.rows.map((row) => ({
4278
- name: row[0],
4279
- type: row[1],
4280
- nullable: row[2] === "YES",
4281
- primaryKey: row[4] === "PRI",
4282
- defaultValue: row[3] === "NULL" ? null : row[3]
4283
- }));
4280
+ const result = exec(buildColumnsSql(table));
4281
+ return columnsFromInfoRows(result.rows);
4284
4282
  }
4285
4283
  const adapter = {
4286
4284
  kind: config.kind,
@@ -4289,7 +4287,7 @@ function createDockerAdapter(config) {
4289
4287
  getTables() {
4290
4288
  let sql;
4291
4289
  if (config.kind === "postgresql") {
4292
- sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name`;
4290
+ sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = ${postgresSchemaLiteral()} ORDER BY table_name`;
4293
4291
  } else {
4294
4292
  sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
4295
4293
  }
@@ -4311,7 +4309,7 @@ function createDockerAdapter(config) {
4311
4309
  getIndexes() {
4312
4310
  let sql;
4313
4311
  if (config.kind === "postgresql") {
4314
- sql = `SELECT indexname, tablename FROM pg_indexes WHERE schemaname = 'public' AND indexname NOT LIKE 'pg_%' ORDER BY indexname`;
4312
+ sql = `SELECT indexname, tablename FROM pg_indexes WHERE schemaname = ${postgresSchemaLiteral()} AND indexname NOT LIKE 'pg_%' ORDER BY indexname`;
4315
4313
  } else {
4316
4314
  sql = `SELECT DISTINCT index_name, table_name, non_unique FROM information_schema.statistics WHERE table_schema = DATABASE() ORDER BY index_name`;
4317
4315
  }
@@ -4334,18 +4332,25 @@ function createDockerAdapter(config) {
4334
4332
  getForeignKeys() {
4335
4333
  let sql;
4336
4334
  if (config.kind === "postgresql") {
4337
- sql = `SELECT tc.table_name, kcu.column_name, ccu.table_name, ccu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = 'public'`;
4335
+ sql = `SELECT tc.table_schema, tc.table_name, kcu.column_name, ccu.table_schema, ccu.table_name, ccu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = ${postgresSchemaLiteral()}`;
4338
4336
  } else {
4339
4337
  sql = `SELECT table_name, column_name, referenced_table_name, referenced_column_name FROM information_schema.key_column_usage WHERE table_schema = DATABASE() AND referenced_table_name IS NOT NULL`;
4340
4338
  }
4341
4339
  try {
4342
4340
  const result = exec(sql);
4343
- return result.rows.map((row) => ({
4341
+ return result.rows.map((row) => config.kind === "postgresql" ? {
4342
+ fromSchema: row[0],
4343
+ fromTable: row[1],
4344
+ fromColumn: row[2],
4345
+ toSchema: row[3],
4346
+ toTable: row[4],
4347
+ toColumn: row[5]
4348
+ } : {
4344
4349
  fromTable: row[0],
4345
4350
  fromColumn: row[1],
4346
4351
  toTable: row[2],
4347
4352
  toColumn: row[3]
4348
- }));
4353
+ });
4349
4354
  } catch {
4350
4355
  return [];
4351
4356
  }
@@ -4363,7 +4368,7 @@ function createDockerAdapter(config) {
4363
4368
  let sql;
4364
4369
  if (config.kind === "postgresql") {
4365
4370
  const inList = uncached.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
4366
- sql = `SELECT table_name, column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name IN (${inList}) ORDER BY table_name, ordinal_position`;
4371
+ sql = `SELECT table_name, column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = ${postgresSchemaLiteral()} AND table_name IN (${inList}) ORDER BY table_name, ordinal_position`;
4367
4372
  } else {
4368
4373
  const inList = uncached.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
4369
4374
  sql = `SELECT table_name, column_name, column_type, is_nullable, column_default, column_key FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name IN (${inList}) ORDER BY table_name, ordinal_position`;
@@ -4381,7 +4386,7 @@ function createDockerAdapter(config) {
4381
4386
  if (config.kind === "postgresql") {
4382
4387
  try {
4383
4388
  const pkInList = uncached.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
4384
- const pkResult = exec(`SELECT c.relname, a.attname FROM pg_index i JOIN pg_class c ON c.oid = i.indrelid JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indisprimary AND c.relname IN (${pkInList})`);
4389
+ const pkResult = exec(`SELECT c.relname, a.attname FROM pg_index i JOIN pg_class c ON c.oid = i.indrelid JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indisprimary AND n.nspname = ${postgresSchemaLiteral()} AND c.relname IN (${pkInList})`);
4385
4390
  for (const row of pkResult.rows) {
4386
4391
  const existing = pkMap.get(row[0]) || new Set;
4387
4392
  existing.add(row[1]);
@@ -4424,7 +4429,7 @@ function createDockerAdapter(config) {
4424
4429
  return result;
4425
4430
  },
4426
4431
  getTableRowCount(table) {
4427
- const id = sanitizeIdentifier(table, config.kind);
4432
+ const id = tableIdentifier(table);
4428
4433
  const result = exec(`SELECT COUNT(*) FROM ${id}`);
4429
4434
  return result.rows.length > 0 ? Number(result.rows[0][0]) || 0 : 0;
4430
4435
  },
@@ -4433,7 +4438,7 @@ function createDockerAdapter(config) {
4433
4438
  if (tables.length === 0)
4434
4439
  return result;
4435
4440
  const parts = tables.map((t) => {
4436
- const id = sanitizeIdentifier(t, config.kind);
4441
+ const id = tableIdentifier(t);
4437
4442
  return `SELECT '${t.replace(/'/g, "''")}' AS tbl, COUNT(*) AS cnt FROM ${id}`;
4438
4443
  });
4439
4444
  const sql = parts.join(" UNION ALL ");
@@ -4444,7 +4449,7 @@ function createDockerAdapter(config) {
4444
4449
  }
4445
4450
  } catch {
4446
4451
  for (const t of tables) {
4447
- const id = sanitizeIdentifier(t, config.kind);
4452
+ const id = tableIdentifier(t);
4448
4453
  try {
4449
4454
  const r = exec(`SELECT COUNT(*) FROM ${id}`);
4450
4455
  result.set(t, r.rows.length > 0 ? Number(r.rows[0][0]) || 0 : 0);
@@ -4456,7 +4461,7 @@ function createDockerAdapter(config) {
4456
4461
  return result;
4457
4462
  },
4458
4463
  async getTablePageWithMeta(table, options) {
4459
- const id = sanitizeIdentifier(table, config.kind);
4464
+ const id = tableIdentifier(table);
4460
4465
  const order = buildOrderClause(options.orderBy, config.kind);
4461
4466
  const countSql = `SELECT COUNT(*) AS cnt FROM ${id}`;
4462
4467
  const columnsPromise = tableMetaCache.getColumns(table, () => fetchColumnsAsyncUncached(table));
@@ -4471,7 +4476,7 @@ function createDockerAdapter(config) {
4471
4476
  return tablePageMetaFromResults(columns, dataResult, totalRows);
4472
4477
  },
4473
4478
  async getFilteredTablePageWithMeta(table, options) {
4474
- const id = sanitizeIdentifier(table, config.kind);
4479
+ const id = tableIdentifier(table);
4475
4480
  const order = buildOrderClause(options.orderBy, config.kind);
4476
4481
  const where = buildDockerFilterWhere(options.grouped, config.kind);
4477
4482
  const whereClause = where ? ` WHERE ${where}` : "";
@@ -4488,7 +4493,7 @@ function createDockerAdapter(config) {
4488
4493
  return tablePageMetaFromResults(columns, dataResult, rowCountFromResult(countResult));
4489
4494
  },
4490
4495
  getTablePage(table, options) {
4491
- const id = sanitizeIdentifier(table, config.kind);
4496
+ const id = tableIdentifier(table);
4492
4497
  const order = buildOrderClause(options.orderBy, config.kind);
4493
4498
  const cols = this.getColumns(table);
4494
4499
  const selectList = buildTableSelectList(cols, config.kind);
@@ -4522,10 +4527,8 @@ function createDockerAdapter(config) {
4522
4527
  if (BLOCKED_RE.test(upper)) {
4523
4528
  throw new Error("Query contains a disallowed statement keyword");
4524
4529
  }
4525
- const readOnlyPreamble = config.kind === "postgresql" ? "BEGIN TRANSACTION READ ONLY; " : "SET SESSION TRANSACTION READ ONLY; ";
4526
- const readOnlyPostamble = config.kind === "postgresql" ? "; COMMIT" : "; SET SESSION TRANSACTION READ WRITE";
4527
4530
  const stripped = trimmed.replace(/;\s*$/, "");
4528
- const limited = `${readOnlyPreamble}${stripped} LIMIT ${maxRows}${readOnlyPostamble}`;
4531
+ const limited = config.kind === "postgresql" ? `BEGIN TRANSACTION READ ONLY; SET LOCAL search_path = ${sanitizeIdentifier(currentPostgresSchema(), config.kind)}; ${stripped} LIMIT ${maxRows}; COMMIT` : `SET SESSION TRANSACTION READ ONLY; ${stripped} LIMIT ${maxRows}; SET SESSION TRANSACTION READ WRITE`;
4529
4532
  const result = exec(limited);
4530
4533
  const columnNames = config.kind === "mysql" && result.columns.length > 0 ? result.columns : result.rows.length > 0 ? Array.from({ length: result.rows[0].length }, (_, i) => `col${i + 1}`) : [];
4531
4534
  return {
@@ -4541,14 +4544,14 @@ function createDockerAdapter(config) {
4541
4544
  getCreateStatement(table) {
4542
4545
  if (config.kind === "mysql") {
4543
4546
  try {
4544
- const result = exec(`SHOW CREATE TABLE ${sanitizeIdentifier(table, config.kind)}`);
4547
+ const result = exec(`SHOW CREATE TABLE ${tableIdentifier(table)}`);
4545
4548
  return result.rows.length > 0 ? result.rows[0][1] || "" : "";
4546
4549
  } catch {
4547
4550
  return "";
4548
4551
  }
4549
4552
  }
4550
4553
  try {
4551
- const result = exec(`SELECT 'CREATE TABLE ' || '${table.replace(/'/g, "''")}' || ' (...)' AS ddl`);
4554
+ const result = exec(`SELECT 'CREATE TABLE ' || ${escapeSqlString(tableIdentifier(table))} || ' (...)' AS ddl`);
4552
4555
  return result.rows.length > 0 ? result.rows[0][0] || "" : "";
4553
4556
  } catch {
4554
4557
  return "";
@@ -4559,7 +4562,7 @@ function createDockerAdapter(config) {
4559
4562
  if (config.kind === "mysql") {
4560
4563
  sql = `SELECT trigger_name, action_statement FROM information_schema.triggers WHERE event_object_schema = DATABASE() AND event_object_table = '${table.replace(/'/g, "''")}'`;
4561
4564
  } else {
4562
- sql = `SELECT tgname, pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = '${table.replace(/'/g, "''")}'::regclass AND NOT tgisinternal`;
4565
+ sql = `SELECT tgname, pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = ${postgresRegclassLiteral(table)}::regclass AND NOT tgisinternal`;
4563
4566
  }
4564
4567
  try {
4565
4568
  const result = exec(sql);
@@ -4617,7 +4620,7 @@ function listDockerDatabases(serviceName, kind, env, cwd) {
4617
4620
  return [...cached.value];
4618
4621
  const containerName = resolveRunningComposeContainerName(serviceName, cwd);
4619
4622
  if (!containerName) {
4620
- return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
4623
+ return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
4621
4624
  }
4622
4625
  const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || env.POSTGRES_USERNAME || env.MYSQL_USERNAME || env.USER || "root";
4623
4626
  const password = env.POSTGRES_PASSWORD || env.MYSQL_PASSWORD || env.MARIADB_PASSWORD || "";
@@ -4654,7 +4657,43 @@ function listDockerDatabases(serviceName, kind, env, cwd) {
4654
4657
  return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
4655
4658
  }
4656
4659
  }
4657
- function openDockerAdapter(serviceName, kind, env, cwd, overrideDatabase) {
4660
+ function listDockerSchemas(serviceName, kind, env, cwd, overrideDatabase) {
4661
+ if (kind !== "postgresql")
4662
+ return [];
4663
+ const user = env.POSTGRES_USER || env.POSTGRES_USERNAME || "postgres";
4664
+ const password = env.POSTGRES_PASSWORD || "";
4665
+ const database = overrideDatabase || env.POSTGRES_DB || "postgres";
4666
+ const cacheKey = dockerSchemasCacheKey(serviceName, kind, cwd, database);
4667
+ const now = Date.now();
4668
+ const cached = dockerSchemasCache.get(cacheKey);
4669
+ if (cached && cached.expiresAt > now)
4670
+ return [...cached.value];
4671
+ const containerName = resolveRunningComposeContainerName(serviceName, cwd);
4672
+ if (!containerName) {
4673
+ return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
4674
+ }
4675
+ const config = {
4676
+ kind,
4677
+ containerName,
4678
+ user,
4679
+ password,
4680
+ database
4681
+ };
4682
+ try {
4683
+ const sql = `SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema') AND schema_name NOT LIKE 'pg_toast%' AND schema_name NOT LIKE 'pg_temp_%' AND schema_name NOT LIKE 'pg_toast_temp_%' AND has_schema_privilege(schema_name, 'USAGE') ORDER BY CASE WHEN schema_name = 'public' THEN 0 ELSE 1 END, schema_name`;
4684
+ const result = execInContainer(config, sql);
4685
+ if (result.code !== 0) {
4686
+ return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
4687
+ }
4688
+ const parsed = parseTsvOutput(result.stdout, false);
4689
+ const schemas = parsed.rows.map((r) => r[0]).filter(Boolean);
4690
+ const value = schemas.length > 0 ? schemas : ["public"];
4691
+ return setDockerSchemasCache(cacheKey, value, DOCKER_DATABASES_POSITIVE_TTL_MS, now);
4692
+ } catch {
4693
+ return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
4694
+ }
4695
+ }
4696
+ function openDockerAdapter(serviceName, kind, env, cwd, overrideDatabase, schema) {
4658
4697
  const containerName = resolveRunningComposeContainerNameOrThrow(serviceName, cwd);
4659
4698
  const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || (kind === "postgresql" ? "postgres" : "root");
4660
4699
  const password = env.POSTGRES_PASSWORD || env.MYSQL_PASSWORD || env.MYSQL_ROOT_PASSWORD || env.MARIADB_PASSWORD || env.MARIADB_ROOT_PASSWORD || "";
@@ -4664,14 +4703,16 @@ function openDockerAdapter(serviceName, kind, env, cwd, overrideDatabase) {
4664
4703
  containerName,
4665
4704
  user,
4666
4705
  password,
4667
- database
4706
+ database,
4707
+ ...kind === "postgresql" && schema ? { schema } : {}
4668
4708
  });
4669
4709
  }
4670
- var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, spawnSyncImpl2, MYSQL_SPATIAL_TYPES;
4710
+ var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, MYSQL_SPATIAL_TYPES;
4671
4711
  var init_docker = __esm(() => {
4672
4712
  init_sql_snapshot();
4673
4713
  init_docker_utils();
4674
4714
  dockerDatabasesCache = new Map;
4715
+ dockerSchemasCache = new Map;
4675
4716
  spawnSyncImpl2 = spawnSync3;
4676
4717
  MYSQL_SPATIAL_TYPES = new Set([
4677
4718
  "geometry",
@@ -5957,6 +5998,12 @@ function createDockerAdapterCache(maxEntries = DEFAULT_MAX_DOCKER_ADAPTER_CACHE,
5957
5998
  const cached = cache.get(key);
5958
5999
  if (cached)
5959
6000
  closeEntry(key, cached);
6001
+ },
6002
+ closePrefix(prefix) {
6003
+ for (const [key, entry] of Array.from(cache)) {
6004
+ if (key.startsWith(prefix))
6005
+ closeEntry(key, entry);
6006
+ }
5960
6007
  }
5961
6008
  };
5962
6009
  }
@@ -7049,12 +7096,18 @@ function deleteQueryHistoryEntry(state, id) {
7049
7096
  entries: state.entries.filter((e) => e.id !== id)
7050
7097
  };
7051
7098
  }
7052
- function clearQueryHistory(state, dbId) {
7099
+ function clearQueryHistory(state, dbId, schema) {
7053
7100
  if (!dbId)
7054
7101
  return emptyState();
7055
7102
  return {
7056
7103
  version: 1,
7057
- entries: state.entries.filter((e) => e.dbId !== dbId)
7104
+ entries: state.entries.filter((e) => {
7105
+ if (e.dbId !== dbId)
7106
+ return true;
7107
+ if (schema === undefined)
7108
+ return false;
7109
+ return (e.schema || "public") !== schema;
7110
+ })
7058
7111
  };
7059
7112
  }
7060
7113
  var CODE_VIEWER_DIR2 = ".code-viewer", HISTORY_FILE_NAME = "query-history.json", MAX_ENTRIES2 = 200, MAX_PREVIEW_ROWS = 100, MAX_JSON_BYTES = 1e6;
@@ -7095,6 +7148,9 @@ async function getStoreDb(cwd) {
7095
7148
  storeDb.exec("PRAGMA journal_mode=WAL");
7096
7149
  storeDb.exec("PRAGMA foreign_keys=ON");
7097
7150
  storeDb.exec(SCHEMA_SQL);
7151
+ try {
7152
+ storeDb.exec("ALTER TABLE snapshots ADD COLUMN schema_name TEXT");
7153
+ } catch {}
7098
7154
  return storeDb;
7099
7155
  }
7100
7156
  function makeId2(prefix) {
@@ -7103,10 +7159,10 @@ function makeId2(prefix) {
7103
7159
  function hashPayload(payloadJson) {
7104
7160
  return createHash4("sha256").update(payloadJson).digest("hex");
7105
7161
  }
7106
- async function createSnapshot(cwd, dbId, kind, tables, note) {
7162
+ async function createSnapshot(cwd, dbId, kind, tables, note, schema) {
7107
7163
  const db = await getStoreDb(cwd);
7108
7164
  const id = makeId2("snap");
7109
- db.prepare("INSERT INTO snapshots (id, db_id, kind, note, created_at, status) VALUES (?, ?, ?, ?, ?, ?)").run(id, dbId, kind, note, new Date().toISOString(), "running");
7165
+ db.prepare("INSERT INTO snapshots (id, db_id, schema_name, kind, note, created_at, status) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, dbId, schema ?? null, kind, note, new Date().toISOString(), "running");
7110
7166
  for (const t of tables) {
7111
7167
  db.prepare("INSERT INTO snapshot_tables (snapshot_id, table_name) VALUES (?, ?)").run(id, t);
7112
7168
  }
@@ -7135,19 +7191,22 @@ async function finalizeSnapshot(cwd, snapshotId, error) {
7135
7191
  db.prepare("UPDATE snapshots SET status = 'done' WHERE id = ?").run(snapshotId);
7136
7192
  }
7137
7193
  }
7138
- async function listSnapshots(cwd, dbId) {
7194
+ async function listSnapshots(cwd, dbId, schema) {
7139
7195
  const db = await getStoreDb(cwd);
7140
7196
  let rows;
7141
- if (dbId) {
7142
- rows = db.prepare("SELECT id, db_id, kind, note, created_at, status, error_message FROM snapshots WHERE db_id = ? ORDER BY created_at DESC").all(dbId);
7197
+ if (dbId && schema !== undefined) {
7198
+ rows = db.prepare("SELECT id, db_id, schema_name, kind, note, created_at, status, error_message FROM snapshots WHERE db_id = ? AND COALESCE(schema_name, 'public') = ? ORDER BY created_at DESC").all(dbId, schema);
7199
+ } else if (dbId) {
7200
+ rows = db.prepare("SELECT id, db_id, schema_name, kind, note, created_at, status, error_message FROM snapshots WHERE db_id = ? ORDER BY created_at DESC").all(dbId);
7143
7201
  } else {
7144
- rows = db.prepare("SELECT id, db_id, kind, note, created_at, status, error_message FROM snapshots ORDER BY created_at DESC").all();
7202
+ rows = db.prepare("SELECT id, db_id, schema_name, kind, note, created_at, status, error_message FROM snapshots ORDER BY created_at DESC").all();
7145
7203
  }
7146
7204
  return rows.map((r) => {
7147
7205
  const tableRows = db.prepare("SELECT table_name FROM snapshot_tables WHERE snapshot_id = ?").all(r.id);
7148
7206
  return {
7149
7207
  id: r.id,
7150
7208
  dbId: r.db_id,
7209
+ ...r.schema_name ? { schema: r.schema_name } : {},
7151
7210
  kind: r.kind,
7152
7211
  note: r.note,
7153
7212
  createdAt: r.created_at,
@@ -7172,8 +7231,22 @@ async function deleteSnapshot(cwd, snapshotId) {
7172
7231
  }
7173
7232
  }
7174
7233
  }
7234
+ function getSnapshotScope(db, snapshotId) {
7235
+ const row = db.prepare("SELECT db_id, COALESCE(schema_name, 'public') AS schema_name FROM snapshots WHERE id = ?").get(snapshotId);
7236
+ if (!row)
7237
+ throw new Error(`snapshot not found: ${snapshotId}`);
7238
+ return { dbId: row.db_id, schema: row.schema_name };
7239
+ }
7240
+ function assertSameSnapshotScope(db, beforeId, afterId) {
7241
+ const before = getSnapshotScope(db, beforeId);
7242
+ const after = getSnapshotScope(db, afterId);
7243
+ if (before.dbId !== after.dbId || before.schema !== after.schema) {
7244
+ throw new Error(`cannot compare snapshots from different database/schema (${before.dbId}:${before.schema} vs ${after.dbId}:${after.schema})`);
7245
+ }
7246
+ }
7175
7247
  async function computeDiffTables(cwd, beforeId, afterId) {
7176
7248
  const db = await getStoreDb(cwd);
7249
+ assertSameSnapshotScope(db, beforeId, afterId);
7177
7250
  const beforeTables = db.prepare("SELECT table_name, table_hash, row_count FROM snapshot_tables WHERE snapshot_id = ?").all(beforeId);
7178
7251
  const afterTables = db.prepare("SELECT table_name, table_hash, row_count FROM snapshot_tables WHERE snapshot_id = ?").all(afterId);
7179
7252
  const beforeMap = new Map(beforeTables.map((t) => [t.table_name, t]));
@@ -7196,7 +7269,7 @@ async function computeDiffTables(cwd, beforeId, afterId) {
7196
7269
  if (!b) {
7197
7270
  results.push({
7198
7271
  tableName: table,
7199
- insertedCount: a.row_count,
7272
+ insertedCount: a ? a.row_count : 0,
7200
7273
  updatedCount: 0,
7201
7274
  deletedCount: 0,
7202
7275
  unchangedCount: 0
@@ -7245,6 +7318,7 @@ async function computeDiffTables(cwd, beforeId, afterId) {
7245
7318
  }
7246
7319
  async function computeDiffRows(cwd, beforeId, afterId, table, offset = 0, limit = 200) {
7247
7320
  const db = await getStoreDb(cwd);
7321
+ assertSameSnapshotScope(db, beforeId, afterId);
7248
7322
  const allDiffRows = [];
7249
7323
  const inserted = db.prepare(`SELECT a.row_key_json, a.payload_hash
7250
7324
  FROM snapshot_rows a
@@ -7314,6 +7388,7 @@ var CODE_VIEWER_DIR3 = ".code-viewer", SNAPSHOT_DB_NAME = "db-snapshots.sqlite",
7314
7388
  CREATE TABLE IF NOT EXISTS snapshots (
7315
7389
  id TEXT PRIMARY KEY,
7316
7390
  db_id TEXT NOT NULL,
7391
+ schema_name TEXT,
7317
7392
  kind TEXT NOT NULL,
7318
7393
  note TEXT NOT NULL DEFAULT '',
7319
7394
  created_at TEXT NOT NULL,
@@ -7367,7 +7442,7 @@ async function runSnapshot(cwd, source, dbId, containers, note, onProgress, opti
7367
7442
  throw new Error("data source does not support snapshot (missing SnapshotIterable capability)");
7368
7443
  }
7369
7444
  const snapshotSource = source;
7370
- const snapshotId = await createSnapshot(cwd, dbId, snapshotSource.kind, containers, note);
7445
+ const snapshotId = await createSnapshot(cwd, dbId, snapshotSource.kind, containers, note, options.schema);
7371
7446
  try {
7372
7447
  options.onSnapshotId?.(snapshotId);
7373
7448
  for (const container of containers) {
@@ -7600,14 +7675,40 @@ function ensureInit() {
7600
7675
  async function getAdapter(r, _cwd) {
7601
7676
  if (r.docker) {
7602
7677
  const docker = r.docker;
7603
- return dockerAdapterCache.getOrOpen(r.dbId, () => openDockerAdapter(docker.serviceName, docker.kind, docker.env, docker.composeDir, docker.database));
7678
+ const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
7679
+ return dockerAdapterCache.getOrOpen(cacheKey, () => openDockerAdapter(docker.serviceName, docker.kind, docker.env, docker.composeDir, docker.database, r.schema));
7604
7680
  }
7605
7681
  return getConnection(r.resolved);
7606
7682
  }
7607
7683
  function sanitizeFilename(name) {
7608
7684
  return name.replace(/["\\\r\n\x00-\x1f]/g, "_");
7609
7685
  }
7610
- function resolveDb(cwd, dbParam, omitDirNames) {
7686
+ function normalizeSchemaParam(value) {
7687
+ if (value === undefined || value === null || value === "")
7688
+ return;
7689
+ if (value.length > MAX_SCHEMA_NAME_LEN) {
7690
+ return textError("invalid schema parameter", 400);
7691
+ }
7692
+ if (/[\x00-\x1f\x7f]/.test(value)) {
7693
+ return textError("invalid schema parameter", 400);
7694
+ }
7695
+ return value;
7696
+ }
7697
+ function resolvePostgresSchema(info, requestedSchema) {
7698
+ if (info.kind !== "postgresql")
7699
+ return;
7700
+ const schemas = listDockerSchemas(info.serviceName, "postgresql", info.env, info.composeDir, info.database);
7701
+ if (requestedSchema) {
7702
+ if (!schemas.includes(requestedSchema)) {
7703
+ return textError(`schema not found: ${requestedSchema}`, 404);
7704
+ }
7705
+ return requestedSchema;
7706
+ }
7707
+ if (schemas.includes("public"))
7708
+ return "public";
7709
+ return schemas[0] || "public";
7710
+ }
7711
+ function resolveDb(cwd, dbParam, omitDirNames, schemaParam) {
7611
7712
  if (!dbParam)
7612
7713
  return textError("missing db parameter", 400);
7613
7714
  if (dbParam.startsWith("docker:")) {
@@ -7624,7 +7725,18 @@ function resolveDb(cwd, dbParam, omitDirNames) {
7624
7725
  return textError("elasticsearch services must use the /_db/elasticsearch/* routes", 400);
7625
7726
  }
7626
7727
  const resolved2 = parsed.database ? { ...info, database: parsed.database } : info;
7627
- return { resolved: dbParam, dbId: dbParam, docker: resolved2 };
7728
+ const requestedSchema = normalizeSchemaParam(schemaParam);
7729
+ if (requestedSchema instanceof Response)
7730
+ return requestedSchema;
7731
+ const schema = resolvePostgresSchema(resolved2, requestedSchema);
7732
+ if (schema instanceof Response)
7733
+ return schema;
7734
+ return {
7735
+ resolved: dbParam,
7736
+ dbId: dbParam,
7737
+ docker: resolved2,
7738
+ ...schema ? { schema } : {}
7739
+ };
7628
7740
  }
7629
7741
  const resolved = validateDbPath(cwd, dbParam);
7630
7742
  if (!resolved)
@@ -7679,8 +7791,24 @@ function handleFiles(cwd, omitDirNames) {
7679
7791
  };
7680
7792
  return json(body);
7681
7793
  }
7794
+ function handleSchemas(cwd, url, omitDirNames) {
7795
+ const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
7796
+ if (r instanceof Response)
7797
+ return r;
7798
+ if (!r.docker || r.docker.kind !== "postgresql") {
7799
+ const body2 = { dbId: r.dbId, schemas: [] };
7800
+ return json(body2);
7801
+ }
7802
+ const schemas = listDockerSchemas(r.docker.serviceName, "postgresql", r.docker.env, r.docker.composeDir, r.docker.database);
7803
+ const body = {
7804
+ dbId: r.dbId,
7805
+ schemas: schemas.map((name) => ({ name })),
7806
+ selectedSchema: r.schema
7807
+ };
7808
+ return json(body);
7809
+ }
7682
7810
  async function handleSchema(cwd, url, omitDirNames) {
7683
- const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
7811
+ const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
7684
7812
  if (r instanceof Response)
7685
7813
  return r;
7686
7814
  const includeColumns = url.searchParams.get("includeColumns") === "1";
@@ -7705,6 +7833,7 @@ async function handleSchema(cwd, url, omitDirNames) {
7705
7833
  const foreignKeys = adapter.getForeignKeys();
7706
7834
  const body = {
7707
7835
  dbId: r.dbId,
7836
+ ...r.schema ? { schema: r.schema } : {},
7708
7837
  tables: tablesWithCount,
7709
7838
  indexes,
7710
7839
  foreignKeys
@@ -7782,7 +7911,7 @@ function groupFiltersByValue(filters) {
7782
7911
  return grouped;
7783
7912
  }
7784
7913
  async function handleTable(cwd, url, omitDirNames) {
7785
- const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
7914
+ const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
7786
7915
  if (r instanceof Response)
7787
7916
  return r;
7788
7917
  const table = url.searchParams.get("table");
@@ -7817,6 +7946,7 @@ async function handleTable(cwd, url, omitDirNames) {
7817
7946
  }
7818
7947
  const body2 = {
7819
7948
  dbId: r.dbId,
7949
+ ...r.schema ? { schema: r.schema } : {},
7820
7950
  table,
7821
7951
  columns: meta.columns,
7822
7952
  rows: serializeDbRows(meta.rows),
@@ -7839,6 +7969,7 @@ async function handleTable(cwd, url, omitDirNames) {
7839
7969
  }
7840
7970
  const body2 = {
7841
7971
  dbId: r.dbId,
7972
+ ...r.schema ? { schema: r.schema } : {},
7842
7973
  table,
7843
7974
  columns: meta.columns,
7844
7975
  rows: serializeDbRows(meta.rows),
@@ -7870,6 +8001,7 @@ async function handleTable(cwd, url, omitDirNames) {
7870
8001
  const dataResult = adapter.executeReadonlyQuery(dataSql, filter.useParams ? [...filter.params, limit, offset] : undefined);
7871
8002
  const body2 = {
7872
8003
  dbId: r.dbId,
8004
+ ...r.schema ? { schema: r.schema } : {},
7873
8005
  table,
7874
8006
  columns,
7875
8007
  rows: serializeDbRows(dataResult.rows),
@@ -7885,6 +8017,7 @@ async function handleTable(cwd, url, omitDirNames) {
7885
8017
  const totalRows = result.rowCount < limit ? offset + result.rowCount : adapter.getTableRowCount(table);
7886
8018
  const body = {
7887
8019
  dbId: r.dbId,
8020
+ ...r.schema ? { schema: r.schema } : {},
7888
8021
  table,
7889
8022
  columns,
7890
8023
  rows: serializeDbRows(result.rows),
@@ -7937,7 +8070,7 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
7937
8070
  return body;
7938
8071
  if (!body.db || !body.sql)
7939
8072
  return textError("missing db or sql", 400);
7940
- const r = resolveDb(cwd, body.db, omitDirNames);
8073
+ const r = resolveDb(cwd, body.db, omitDirNames, body.schema);
7941
8074
  if (r instanceof Response)
7942
8075
  return r;
7943
8076
  const maxRows = Math.min(1e4, Math.max(1, body.maxRows || 1000));
@@ -7952,6 +8085,7 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
7952
8085
  const columnTypes = inferredColumns.length > 0 ? inferredColumns.map((col) => col.type) : result.columnTypes;
7953
8086
  const response = {
7954
8087
  dbId: body.db,
8088
+ ...r.schema ? { schema: r.schema } : {},
7955
8089
  columns,
7956
8090
  columnTypes,
7957
8091
  rows: serializedRows,
@@ -7963,6 +8097,7 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
7963
8097
  const entry = {
7964
8098
  id: makeHistoryId(),
7965
8099
  dbId: body.db,
8100
+ ...r.schema ? { schema: r.schema } : {},
7966
8101
  sql: body.sql,
7967
8102
  title: body.title,
7968
8103
  body: body.body,
@@ -7979,7 +8114,12 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
7979
8114
  const state = loadQueryHistory(cwd);
7980
8115
  const updated = addQueryHistoryEntry(state, entry);
7981
8116
  saveQueryHistory(cwd, updated);
7982
- sendSse?.("db-query", JSON.stringify({ action: "add", dbId: body.db, id: entry.id }));
8117
+ sendSse?.("db-query", JSON.stringify({
8118
+ action: "add",
8119
+ dbId: body.db,
8120
+ schema: r.schema,
8121
+ id: entry.id
8122
+ }));
7983
8123
  }
7984
8124
  return json(response);
7985
8125
  } catch (err) {
@@ -7990,6 +8130,7 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
7990
8130
  const elapsed = Date.now() - start;
7991
8131
  const response = {
7992
8132
  dbId: body.db,
8133
+ ...r.schema ? { schema: r.schema } : {},
7993
8134
  columns: [],
7994
8135
  columnTypes: [],
7995
8136
  rows: [],
@@ -8003,11 +8144,20 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
8003
8144
  }
8004
8145
  function handleHistory(cwd, url) {
8005
8146
  const dbId = url.searchParams.get("db") || undefined;
8147
+ const schema = normalizeSchemaParam(url.searchParams.get("schema"));
8148
+ if (schema instanceof Response)
8149
+ return schema;
8006
8150
  const state = loadQueryHistory(cwd);
8007
8151
  if (dbId) {
8008
8152
  return json({
8009
8153
  version: 1,
8010
- entries: state.entries.filter((e) => e.dbId === dbId)
8154
+ entries: state.entries.filter((e) => {
8155
+ if (e.dbId !== dbId)
8156
+ return false;
8157
+ if (schema === undefined)
8158
+ return true;
8159
+ return (e.schema || "public") === schema;
8160
+ })
8011
8161
  });
8012
8162
  }
8013
8163
  return json(state);
@@ -8022,7 +8172,12 @@ async function handleHistoryDelete(cwd, req, sendSse) {
8022
8172
  const deleted = state.entries.find((entry) => entry.id === body.id);
8023
8173
  const updated = deleteQueryHistoryEntry(state, body.id);
8024
8174
  saveQueryHistory(cwd, updated);
8025
- sendSse?.("db-query", JSON.stringify({ action: "delete", dbId: deleted?.dbId, id: body.id }));
8175
+ sendSse?.("db-query", JSON.stringify({
8176
+ action: "delete",
8177
+ dbId: deleted?.dbId,
8178
+ schema: deleted?.schema,
8179
+ id: body.id
8180
+ }));
8026
8181
  return json({ ok: true });
8027
8182
  }
8028
8183
  async function handleHistoryClear(cwd, req, sendSse) {
@@ -8035,9 +8190,12 @@ async function handleHistoryClear(cwd, req, sendSse) {
8035
8190
  body = {};
8036
8191
  }
8037
8192
  const state = loadQueryHistory(cwd);
8038
- const updated = clearQueryHistory(state, body.db);
8193
+ const schema = normalizeSchemaParam(body.schema);
8194
+ if (schema instanceof Response)
8195
+ return schema;
8196
+ const updated = clearQueryHistory(state, body.db, schema);
8039
8197
  saveQueryHistory(cwd, updated);
8040
- sendSse?.("db-query", JSON.stringify({ action: "clear", dbId: body.db }));
8198
+ sendSse?.("db-query", JSON.stringify({ action: "clear", dbId: body.db, schema }));
8041
8199
  return json({ ok: true });
8042
8200
  }
8043
8201
  function formatCsvField(value) {
@@ -8055,7 +8213,7 @@ function formatCsvField(value) {
8055
8213
  return str;
8056
8214
  }
8057
8215
  async function handleExport(cwd, url, omitDirNames) {
8058
- const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
8216
+ const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
8059
8217
  if (r instanceof Response)
8060
8218
  return r;
8061
8219
  const table = url.searchParams.get("table");
@@ -8156,7 +8314,7 @@ async function handleExport(cwd, url, omitDirNames) {
8156
8314
  }
8157
8315
  }
8158
8316
  async function handleColumns(cwd, url, omitDirNames) {
8159
- const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
8317
+ const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
8160
8318
  if (r instanceof Response)
8161
8319
  return r;
8162
8320
  const table = url.searchParams.get("table");
@@ -8165,13 +8323,18 @@ async function handleColumns(cwd, url, omitDirNames) {
8165
8323
  try {
8166
8324
  const adapter = await getAdapter(r, cwd);
8167
8325
  const columns = adapter.getColumns(table);
8168
- return json({ dbId: r.dbId, table, columns });
8326
+ return json({
8327
+ dbId: r.dbId,
8328
+ ...r.schema ? { schema: r.schema } : {},
8329
+ table,
8330
+ columns
8331
+ });
8169
8332
  } catch (err) {
8170
8333
  return handleError("database", "get columns", err);
8171
8334
  }
8172
8335
  }
8173
8336
  async function handleDdl(cwd, url, omitDirNames) {
8174
- const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
8337
+ const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
8175
8338
  if (r instanceof Response)
8176
8339
  return r;
8177
8340
  const table = url.searchParams.get("table");
@@ -8181,7 +8344,13 @@ async function handleDdl(cwd, url, omitDirNames) {
8181
8344
  const adapter = await getAdapter(r, cwd);
8182
8345
  const sql = adapter.getCreateStatement(table);
8183
8346
  const triggers = adapter.getTriggers(table);
8184
- return json({ dbId: r.dbId, table, sql, triggers });
8347
+ return json({
8348
+ dbId: r.dbId,
8349
+ ...r.schema ? { schema: r.schema } : {},
8350
+ table,
8351
+ sql,
8352
+ triggers
8353
+ });
8185
8354
  } catch (err) {
8186
8355
  return handleError("database", "get DDL", err);
8187
8356
  }
@@ -8192,7 +8361,7 @@ async function handleSearchStart(cwd, req, omitDirNames) {
8192
8361
  return body;
8193
8362
  if (!body.db || !body.term)
8194
8363
  return textError("missing db or term", 400);
8195
- const r = resolveDb(cwd, body.db, omitDirNames);
8364
+ const r = resolveDb(cwd, body.db, omitDirNames, body.schema);
8196
8365
  if (r instanceof Response)
8197
8366
  return r;
8198
8367
  const jobId = makeId("search");
@@ -8200,6 +8369,7 @@ async function handleSearchStart(cwd, req, omitDirNames) {
8200
8369
  const job = {
8201
8370
  id: jobId,
8202
8371
  dbId: body.db,
8372
+ ...r.schema ? { schema: r.schema } : {},
8203
8373
  scannedTables: 0,
8204
8374
  totalTables: 0,
8205
8375
  hits: [],
@@ -8237,7 +8407,10 @@ async function handleSearchStart(cwd, req, omitDirNames) {
8237
8407
  const pkCols = getPrimaryKeyColumns(adapter, table);
8238
8408
  const columns = adapter.getColumns(table);
8239
8409
  const hits = searchTable(adapter, table, columns, term, maxHitsPerTable, includeNonText, pkCols);
8240
- job.hits.push(...hits);
8410
+ job.hits.push(...hits.map((hit) => ({
8411
+ ...r.schema ? { schema: r.schema } : {},
8412
+ ...hit
8413
+ })));
8241
8414
  job.scannedTables++;
8242
8415
  }
8243
8416
  job.done = true;
@@ -8261,6 +8434,7 @@ function handleSearchStatus(url) {
8261
8434
  const result = {
8262
8435
  jobId: job.id,
8263
8436
  dbId: job.dbId,
8437
+ schema: job.schema,
8264
8438
  scannedTables: job.scannedTables,
8265
8439
  totalTables: job.totalTables,
8266
8440
  currentTable: job.currentTable,
@@ -8292,7 +8466,10 @@ async function openRegisteredDockerSnapshotSource(info, requestedContainers) {
8292
8466
  }
8293
8467
  async function handleSnapshotList(cwd, url) {
8294
8468
  const dbId = url.searchParams.get("db") || undefined;
8295
- const snapshots = await listSnapshots(cwd, dbId);
8469
+ const schema = normalizeSchemaParam(url.searchParams.get("schema"));
8470
+ if (schema instanceof Response)
8471
+ return schema;
8472
+ const snapshots = await listSnapshots(cwd, dbId, schema);
8296
8473
  return json({ snapshots });
8297
8474
  }
8298
8475
  function sanitizeSnapshotTables(tables) {
@@ -8343,10 +8520,11 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
8343
8520
  }
8344
8521
  }
8345
8522
  if (!source) {
8346
- const r = resolveDb(cwd, body.db, omitDirNames);
8523
+ const r = resolveDb(cwd, body.db, omitDirNames, body.schema);
8347
8524
  if (r instanceof Response)
8348
8525
  return r;
8349
8526
  source = await getAdapter(r, cwd);
8527
+ body.schema = r.schema;
8350
8528
  }
8351
8529
  if (!containers || containers.length === 0) {
8352
8530
  const sqlAdapter = source;
@@ -8373,11 +8551,13 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
8373
8551
  sendSse?.("db-snapshot", JSON.stringify({
8374
8552
  action: "progress",
8375
8553
  dbId: snapshotDbId,
8554
+ schema: body.schema,
8376
8555
  table,
8377
8556
  done
8378
8557
  }));
8379
8558
  }, {
8380
8559
  signal: abortController.signal,
8560
+ schema: body.schema,
8381
8561
  onSnapshotId: (id) => {
8382
8562
  activeSnapshotId = id;
8383
8563
  snapshotJob.snapshotId = id;
@@ -8385,6 +8565,7 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
8385
8565
  sendSse?.("db-snapshot", JSON.stringify({
8386
8566
  action: "started",
8387
8567
  dbId: snapshotDbId,
8568
+ schema: body.schema,
8388
8569
  id
8389
8570
  }));
8390
8571
  }
@@ -8392,6 +8573,7 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
8392
8573
  sendSse?.("db-snapshot", JSON.stringify({
8393
8574
  action: "created",
8394
8575
  dbId: snapshotDbId,
8576
+ schema: body.schema,
8395
8577
  id: snapshotId
8396
8578
  }));
8397
8579
  } catch (err) {
@@ -8399,6 +8581,7 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
8399
8581
  sendSse?.("db-snapshot", JSON.stringify({
8400
8582
  action: "error",
8401
8583
  dbId: snapshotDbId,
8584
+ schema: body.schema,
8402
8585
  error: err instanceof Error ? err.message : String(err)
8403
8586
  }));
8404
8587
  } finally {
@@ -8523,6 +8706,7 @@ async function handleClose(cwd, req, omitDirNames) {
8523
8706
  const info = findDockerServiceByDbId(cwd, body.db, undefined, omitDirNames);
8524
8707
  if (!info) {
8525
8708
  dockerAdapterCache.close(body.db);
8709
+ dockerAdapterCache.closePrefix(`${body.db}\x00`);
8526
8710
  closeRedisAdapter(body.db);
8527
8711
  closeElasticsearchAdapter(body.db);
8528
8712
  return json({ ok: true });
@@ -8535,6 +8719,7 @@ async function handleClose(cwd, req, omitDirNames) {
8535
8719
  return r;
8536
8720
  if (r.docker) {
8537
8721
  dockerAdapterCache.close(r.dbId);
8722
+ dockerAdapterCache.closePrefix(`${r.dbId}\x00`);
8538
8723
  } else {
8539
8724
  closeConnection(r.resolved);
8540
8725
  }
@@ -8567,6 +8752,10 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
8567
8752
  methods: ["GET"],
8568
8753
  handler: () => handleFiles(cwd, omitDirNames)
8569
8754
  },
8755
+ "/_db/schemas": {
8756
+ methods: ["GET"],
8757
+ handler: () => handleSchemas(cwd, url, omitDirNames)
8758
+ },
8570
8759
  "/_db/schema": {
8571
8760
  methods: ["GET"],
8572
8761
  handler: () => handleSchema(cwd, url, omitDirNames)
@@ -8664,7 +8853,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
8664
8853
  }
8665
8854
  }, sideEffectAllowed, wrapResponse, (err) => handleError("database", "handle database request", err));
8666
8855
  }
8667
- var initialized = false, dockerAdapterCache, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
8856
+ var initialized = false, dockerAdapterCache, MAX_SCHEMA_NAME_LEN = 1024, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
8668
8857
  var init_handle = __esm(() => {
8669
8858
  init_docker();
8670
8859
  init_docker_utils();
@@ -8684,8 +8873,14 @@ var init_handle = __esm(() => {
8684
8873
  searchJobs = new Map;
8685
8874
  snapshotJobs = new Map;
8686
8875
  DOCKER_CLOSE_REGISTRY = {
8687
- postgresql: (dbId) => dockerAdapterCache.close(dbId),
8688
- mysql: (dbId) => dockerAdapterCache.close(dbId),
8876
+ postgresql: (dbId) => {
8877
+ dockerAdapterCache.close(dbId);
8878
+ dockerAdapterCache.closePrefix(`${dbId}\x00`);
8879
+ },
8880
+ mysql: (dbId) => {
8881
+ dockerAdapterCache.close(dbId);
8882
+ dockerAdapterCache.closePrefix(`${dbId}\x00`);
8883
+ },
8689
8884
  redis: closeRedisAdapter,
8690
8885
  elasticsearch: closeElasticsearchAdapter
8691
8886
  };