@youtyan/code-viewer 0.2.2 → 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.
- package/dist/code-viewer.js +585 -89
- package/package.json +11 -1
- package/web/app.js +224 -46
- package/web/style.css +3 -0
package/web/app.js
CHANGED
|
@@ -633,12 +633,14 @@
|
|
|
633
633
|
}
|
|
634
634
|
case "/database": {
|
|
635
635
|
const db = params.get("db") || undefined;
|
|
636
|
+
const schema = params.get("schema") || undefined;
|
|
636
637
|
const table = params.get("table") || undefined;
|
|
637
638
|
const tabRaw = params.get("tab");
|
|
638
639
|
const tab = tabRaw === "data" || tabRaw === "query" || tabRaw === "schema" || tabRaw === "er" || tabRaw === "search" || tabRaw === "snapshot" ? tabRaw : undefined;
|
|
639
640
|
return {
|
|
640
641
|
screen: "database",
|
|
641
642
|
...db ? { db } : {},
|
|
643
|
+
...schema ? { schema } : {},
|
|
642
644
|
...table ? { table } : {},
|
|
643
645
|
...tab ? { tab } : {},
|
|
644
646
|
range
|
|
@@ -694,6 +696,8 @@
|
|
|
694
696
|
const params = new URLSearchParams;
|
|
695
697
|
if (route.db)
|
|
696
698
|
params.set("db", route.db);
|
|
699
|
+
if (route.schema)
|
|
700
|
+
params.set("schema", route.schema);
|
|
697
701
|
if (route.table)
|
|
698
702
|
params.set("table", route.table);
|
|
699
703
|
if (route.tab)
|
|
@@ -7303,6 +7307,8 @@ ${frontmatter.yaml}
|
|
|
7303
7307
|
const parts = ["Database"];
|
|
7304
7308
|
if (entry.target.db)
|
|
7305
7309
|
parts.push(entry.target.db);
|
|
7310
|
+
if (entry.target.schema)
|
|
7311
|
+
parts.push(entry.target.schema);
|
|
7306
7312
|
if (entry.target.table)
|
|
7307
7313
|
parts.push(entry.target.table);
|
|
7308
7314
|
if (entry.target.tab)
|
|
@@ -7326,6 +7332,8 @@ ${frontmatter.yaml}
|
|
|
7326
7332
|
const target = entry.target;
|
|
7327
7333
|
if (target.db && target.db !== route.db)
|
|
7328
7334
|
return false;
|
|
7335
|
+
if (target.schema && target.schema !== route.schema)
|
|
7336
|
+
return false;
|
|
7329
7337
|
if (target.table && target.table !== route.table)
|
|
7330
7338
|
return false;
|
|
7331
7339
|
if (target.tab && target.tab !== (route.tab || "data"))
|
|
@@ -7647,7 +7655,9 @@ ${frontmatter.yaml}
|
|
|
7647
7655
|
return firstLine.length > 90 ? `${firstLine.slice(0, 90)}…` : firstLine;
|
|
7648
7656
|
}
|
|
7649
7657
|
function databaseAnnotationTitle(target) {
|
|
7650
|
-
const parts = [target.table || target.db || "Database"];
|
|
7658
|
+
const parts = [target.table || target.schema || target.db || "Database"];
|
|
7659
|
+
if (target.schema && target.table)
|
|
7660
|
+
parts.unshift(target.schema);
|
|
7651
7661
|
if (target.tab === "data" && target.data?.search)
|
|
7652
7662
|
parts.push(`search: ${target.data.search}`);
|
|
7653
7663
|
else if (target.tab === "query" && target.query?.sql)
|
|
@@ -7954,6 +7964,7 @@ ${frontmatter.yaml}
|
|
|
7954
7964
|
deps.setRoute({
|
|
7955
7965
|
screen: "database",
|
|
7956
7966
|
db: target.db,
|
|
7967
|
+
schema: target.schema,
|
|
7957
7968
|
table: target.table,
|
|
7958
7969
|
tab: target.tab,
|
|
7959
7970
|
range: deps.currentRange()
|
|
@@ -8481,6 +8492,9 @@ ${frontmatter.yaml}
|
|
|
8481
8492
|
function isMakefileName(name) {
|
|
8482
8493
|
return /^makefile(?:[.-].+)?$/i.test(name);
|
|
8483
8494
|
}
|
|
8495
|
+
function isDotenvName(name) {
|
|
8496
|
+
return /^(?:\.?env|.*\.env)(?:[.-].+)?$/i.test(name);
|
|
8497
|
+
}
|
|
8484
8498
|
function sourceDisplayKind(path) {
|
|
8485
8499
|
if (isVideo(path))
|
|
8486
8500
|
return "video";
|
|
@@ -8496,6 +8510,8 @@ ${frontmatter.yaml}
|
|
|
8496
8510
|
return "text";
|
|
8497
8511
|
if (TEXT_SOURCE_FILENAMES.has(name))
|
|
8498
8512
|
return "text";
|
|
8513
|
+
if (isDotenvName(name))
|
|
8514
|
+
return "text";
|
|
8499
8515
|
if (isDockerfileName(name) || isMakefileName(name))
|
|
8500
8516
|
return "text";
|
|
8501
8517
|
return "unsupported";
|
|
@@ -9391,6 +9407,7 @@ ${frontmatter.yaml}
|
|
|
9391
9407
|
if (disposed)
|
|
9392
9408
|
return;
|
|
9393
9409
|
const dbId = deps.getDbId();
|
|
9410
|
+
const schema = deps.getSchema();
|
|
9394
9411
|
if (!dbId)
|
|
9395
9412
|
return;
|
|
9396
9413
|
const term = input.value.trim();
|
|
@@ -9411,6 +9428,7 @@ ${frontmatter.yaml}
|
|
|
9411
9428
|
},
|
|
9412
9429
|
body: JSON.stringify({
|
|
9413
9430
|
db: dbId,
|
|
9431
|
+
...schema ? { schema } : {},
|
|
9414
9432
|
term,
|
|
9415
9433
|
includeNonText: nonTextCheck.checked
|
|
9416
9434
|
})
|
|
@@ -9489,16 +9507,17 @@ ${frontmatter.yaml}
|
|
|
9489
9507
|
return;
|
|
9490
9508
|
const grouped = new Map;
|
|
9491
9509
|
for (const h of hits) {
|
|
9492
|
-
const
|
|
9510
|
+
const key = h.schema ? `${h.schema}.${h.table}` : h.table;
|
|
9511
|
+
const existing = grouped.get(key) || [];
|
|
9493
9512
|
existing.push(h);
|
|
9494
|
-
grouped.set(
|
|
9513
|
+
grouped.set(key, existing);
|
|
9495
9514
|
}
|
|
9496
|
-
for (const [
|
|
9515
|
+
for (const [tableLabel, tableHits] of grouped) {
|
|
9497
9516
|
const section = document.createElement("div");
|
|
9498
9517
|
section.className = "db-search-table-section";
|
|
9499
9518
|
const tableHeader = document.createElement("div");
|
|
9500
9519
|
tableHeader.className = "db-search-table-header";
|
|
9501
|
-
tableHeader.textContent = `${
|
|
9520
|
+
tableHeader.textContent = `${tableLabel} (${tableHits.length}件)`;
|
|
9502
9521
|
section.appendChild(tableHeader);
|
|
9503
9522
|
const hitsList = document.createElement("div");
|
|
9504
9523
|
hitsList.className = "db-search-hits-list";
|
|
@@ -9963,7 +9982,13 @@ ${frontmatter.yaml}
|
|
|
9963
9982
|
}
|
|
9964
9983
|
async function refresh() {
|
|
9965
9984
|
const dbId = callbacks.getDbId();
|
|
9966
|
-
const
|
|
9985
|
+
const schema = callbacks.getSchema();
|
|
9986
|
+
const searchParams = new URLSearchParams;
|
|
9987
|
+
if (dbId)
|
|
9988
|
+
searchParams.set("db", dbId);
|
|
9989
|
+
if (schema)
|
|
9990
|
+
searchParams.set("schema", schema);
|
|
9991
|
+
const params = searchParams.toString() ? `?${searchParams.toString()}` : "";
|
|
9967
9992
|
try {
|
|
9968
9993
|
const res = await fetch(`/_db/history${params}`);
|
|
9969
9994
|
if (!res.ok)
|
|
@@ -10176,13 +10201,14 @@ ${frontmatter.yaml}
|
|
|
10176
10201
|
clearConfirmTimer = null;
|
|
10177
10202
|
}
|
|
10178
10203
|
try {
|
|
10204
|
+
const schema = callbacks.getSchema();
|
|
10179
10205
|
await fetch("/_db/history/clear", {
|
|
10180
10206
|
method: "POST",
|
|
10181
10207
|
headers: {
|
|
10182
10208
|
"Content-Type": "application/json",
|
|
10183
10209
|
"X-Code-Viewer-Action": "1"
|
|
10184
10210
|
},
|
|
10185
|
-
body: JSON.stringify(dbId ? { db: dbId } : {})
|
|
10211
|
+
body: JSON.stringify(dbId ? { db: dbId, ...schema ? { schema } : {} } : {})
|
|
10186
10212
|
});
|
|
10187
10213
|
entries = [];
|
|
10188
10214
|
render();
|
|
@@ -11012,6 +11038,7 @@ ${frontmatter.yaml}
|
|
|
11012
11038
|
});
|
|
11013
11039
|
confirmBtn.addEventListener("click", async () => {
|
|
11014
11040
|
const dbId = deps.getDbId();
|
|
11041
|
+
const schema = deps.getSchema();
|
|
11015
11042
|
if (!dbId)
|
|
11016
11043
|
return;
|
|
11017
11044
|
const tables = getSelectedTables();
|
|
@@ -11022,11 +11049,12 @@ ${frontmatter.yaml}
|
|
|
11022
11049
|
try {
|
|
11023
11050
|
await postJson("/_db/snapshot/create", {
|
|
11024
11051
|
db: dbId,
|
|
11052
|
+
...schema ? { schema } : {},
|
|
11025
11053
|
tables,
|
|
11026
11054
|
note: noteInput.value.trim()
|
|
11027
11055
|
});
|
|
11028
11056
|
tableSelector.hidden = true;
|
|
11029
|
-
scheduleAutoRefresh(dbId);
|
|
11057
|
+
scheduleAutoRefresh(dbId, schema);
|
|
11030
11058
|
} catch {} finally {
|
|
11031
11059
|
if (!disposed) {
|
|
11032
11060
|
confirmBtn.disabled = false;
|
|
@@ -11034,11 +11062,12 @@ ${frontmatter.yaml}
|
|
|
11034
11062
|
}
|
|
11035
11063
|
}
|
|
11036
11064
|
});
|
|
11037
|
-
function scheduleAutoRefresh(dbId) {
|
|
11065
|
+
function scheduleAutoRefresh(dbId, schema) {
|
|
11038
11066
|
const timer = setTimeout(() => {
|
|
11039
11067
|
autoRefreshTimers.delete(timer);
|
|
11040
|
-
if (disposed || deps.getDbId() !== dbId)
|
|
11068
|
+
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema) {
|
|
11041
11069
|
return;
|
|
11070
|
+
}
|
|
11042
11071
|
refreshAndAutoDiff();
|
|
11043
11072
|
}, 3000);
|
|
11044
11073
|
autoRefreshTimers.add(timer);
|
|
@@ -11046,17 +11075,22 @@ ${frontmatter.yaml}
|
|
|
11046
11075
|
refreshBtn.addEventListener("click", refresh);
|
|
11047
11076
|
async function refresh() {
|
|
11048
11077
|
const dbId = deps.getDbId();
|
|
11078
|
+
const schema = deps.getSchema();
|
|
11049
11079
|
if (!dbId)
|
|
11050
11080
|
return;
|
|
11051
11081
|
try {
|
|
11052
|
-
const
|
|
11082
|
+
const params = new URLSearchParams({ db: dbId });
|
|
11083
|
+
if (schema)
|
|
11084
|
+
params.set("schema", schema);
|
|
11085
|
+
const snapRes = await fetch(`/_db/snapshot/list?${params}`);
|
|
11053
11086
|
if (snapRes.ok) {
|
|
11054
11087
|
const data = await snapRes.json();
|
|
11055
|
-
if (disposed || deps.getDbId() !== dbId)
|
|
11088
|
+
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema) {
|
|
11056
11089
|
return;
|
|
11090
|
+
}
|
|
11057
11091
|
snapshots = data.snapshots;
|
|
11058
11092
|
}
|
|
11059
|
-
if (disposed || deps.getDbId() !== dbId)
|
|
11093
|
+
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema)
|
|
11060
11094
|
return;
|
|
11061
11095
|
renderMain();
|
|
11062
11096
|
} catch {}
|
|
@@ -11431,8 +11465,11 @@ ${frontmatter.yaml}
|
|
|
11431
11465
|
try {
|
|
11432
11466
|
const parsed = JSON.parse(data);
|
|
11433
11467
|
const dbId = deps.getDbId();
|
|
11468
|
+
const schema = deps.getSchema();
|
|
11434
11469
|
if (parsed.dbId && dbId && parsed.dbId !== dbId)
|
|
11435
11470
|
return;
|
|
11471
|
+
if (parsed.schema && parsed.schema !== schema)
|
|
11472
|
+
return;
|
|
11436
11473
|
if (parsed.action === "started" && parsed.id) {
|
|
11437
11474
|
setActiveSnapshotId(parsed.id);
|
|
11438
11475
|
}
|
|
@@ -12395,6 +12432,9 @@ ${frontmatter.yaml}
|
|
|
12395
12432
|
function isSqlKind(kind) {
|
|
12396
12433
|
return kind === "sqlite" || kind === "postgresql" || kind === "mysql";
|
|
12397
12434
|
}
|
|
12435
|
+
function isPostgresKind(kind) {
|
|
12436
|
+
return kind === "postgresql";
|
|
12437
|
+
}
|
|
12398
12438
|
function isSqlView(view) {
|
|
12399
12439
|
return view === "data" || view === "query" || view === "schema" || view === "er" || view === "search" || view === "snapshot";
|
|
12400
12440
|
}
|
|
@@ -12448,14 +12488,19 @@ ${frontmatter.yaml}
|
|
|
12448
12488
|
let currentDbInfo = null;
|
|
12449
12489
|
let schemaCache = null;
|
|
12450
12490
|
let lastFiles = [];
|
|
12491
|
+
let currentSchema = initial.schema ?? null;
|
|
12451
12492
|
let currentTable = initial.table ?? null;
|
|
12452
12493
|
let loadGeneration = 0;
|
|
12453
12494
|
const dbSelect = document.createElement("select");
|
|
12454
12495
|
dbSelect.className = "db-file-select";
|
|
12455
12496
|
dbSelect.title = "Select database file";
|
|
12497
|
+
const schemaSelect = document.createElement("select");
|
|
12498
|
+
schemaSelect.className = "db-file-select db-schema-select";
|
|
12499
|
+
schemaSelect.title = "Select PostgreSQL schema";
|
|
12500
|
+
schemaSelect.hidden = true;
|
|
12456
12501
|
const dbToolbar = document.createElement("div");
|
|
12457
12502
|
dbToolbar.className = "db-toolbar";
|
|
12458
|
-
dbToolbar.
|
|
12503
|
+
dbToolbar.append(dbSelect, schemaSelect);
|
|
12459
12504
|
const tabBar = document.createElement("div");
|
|
12460
12505
|
tabBar.className = "db-tab-bar";
|
|
12461
12506
|
const tabData = createInnerTab("Data", true);
|
|
@@ -12546,14 +12591,17 @@ ${frontmatter.yaml}
|
|
|
12546
12591
|
const schemaView = createSchemaView();
|
|
12547
12592
|
const erDiagram = createErDiagram();
|
|
12548
12593
|
const globalSearchView = createGlobalSearchView({
|
|
12549
|
-
getDbId: () => currentDbInfo?.id || null
|
|
12594
|
+
getDbId: () => currentDbInfo?.id || null,
|
|
12595
|
+
getSchema: () => currentSchema
|
|
12550
12596
|
});
|
|
12551
12597
|
const snapshotView = createSnapshotView({
|
|
12552
12598
|
getDbId: () => currentDbInfo?.id || null,
|
|
12599
|
+
getSchema: () => currentSchema,
|
|
12553
12600
|
getTables: () => schemaCache?.tables || []
|
|
12554
12601
|
});
|
|
12555
12602
|
const historyView = createQueryHistoryView({
|
|
12556
12603
|
getDbId: () => currentDbInfo?.id || null,
|
|
12604
|
+
getSchema: () => currentSchema,
|
|
12557
12605
|
copySqlToQuery: (sql) => {
|
|
12558
12606
|
queryEditor.setSql(sql);
|
|
12559
12607
|
setActiveTab("query");
|
|
@@ -12695,6 +12743,7 @@ ${frontmatter.yaml}
|
|
|
12695
12743
|
deps.setRoute({
|
|
12696
12744
|
screen: "database",
|
|
12697
12745
|
db: currentDbInfo?.id,
|
|
12746
|
+
schema: currentSchema ?? undefined,
|
|
12698
12747
|
table: currentTable ?? undefined,
|
|
12699
12748
|
tab: currentTab === "data" ? undefined : currentTab,
|
|
12700
12749
|
range: deps.currentRange()
|
|
@@ -12715,8 +12764,40 @@ ${frontmatter.yaml}
|
|
|
12715
12764
|
const data = await res.json();
|
|
12716
12765
|
return data;
|
|
12717
12766
|
}
|
|
12767
|
+
function withCurrentSchema(params) {
|
|
12768
|
+
if (currentSchema)
|
|
12769
|
+
params.set("schema", currentSchema);
|
|
12770
|
+
return params;
|
|
12771
|
+
}
|
|
12772
|
+
async function fetchSchemas(dbId, preferredSchema) {
|
|
12773
|
+
const params = new URLSearchParams({ db: dbId });
|
|
12774
|
+
if (preferredSchema)
|
|
12775
|
+
params.set("schema", preferredSchema);
|
|
12776
|
+
const res = await deps.trackLoad(fetch(`/_db/schemas?${params}`));
|
|
12777
|
+
if (!res.ok) {
|
|
12778
|
+
throw new Error(await responseErrorMessage(res, "failed to fetch schemas"));
|
|
12779
|
+
}
|
|
12780
|
+
return await res.json();
|
|
12781
|
+
}
|
|
12782
|
+
function renderSchemaOptions(schemas, selected) {
|
|
12783
|
+
schemaSelect.innerHTML = "";
|
|
12784
|
+
for (const schema of schemas) {
|
|
12785
|
+
const opt = document.createElement("option");
|
|
12786
|
+
opt.value = schema;
|
|
12787
|
+
opt.textContent = schema;
|
|
12788
|
+
schemaSelect.appendChild(opt);
|
|
12789
|
+
}
|
|
12790
|
+
schemaSelect.hidden = schemas.length === 0;
|
|
12791
|
+
schemaSelect.disabled = schemas.length === 0;
|
|
12792
|
+
if (selected && schemas.includes(selected)) {
|
|
12793
|
+
schemaSelect.value = selected;
|
|
12794
|
+
} else if (schemas.length > 0) {
|
|
12795
|
+
schemaSelect.value = schemas[0];
|
|
12796
|
+
}
|
|
12797
|
+
}
|
|
12718
12798
|
async function fetchSchema(dbId) {
|
|
12719
|
-
const
|
|
12799
|
+
const params = withCurrentSchema(new URLSearchParams({ db: dbId, includeColumns: "1" }));
|
|
12800
|
+
const res = await deps.trackLoad(fetch(`/_db/schema?${params}`));
|
|
12720
12801
|
if (!res.ok) {
|
|
12721
12802
|
throw new Error(await responseErrorMessage(res, "failed to fetch schema"));
|
|
12722
12803
|
}
|
|
@@ -12731,6 +12812,7 @@ ${frontmatter.yaml}
|
|
|
12731
12812
|
offset: String(offset),
|
|
12732
12813
|
limit: String(limit)
|
|
12733
12814
|
});
|
|
12815
|
+
withCurrentSchema(params);
|
|
12734
12816
|
if (sort) {
|
|
12735
12817
|
params.set("sort", sort.column);
|
|
12736
12818
|
params.set("dir", sort.direction);
|
|
@@ -12755,6 +12837,7 @@ ${frontmatter.yaml}
|
|
|
12755
12837
|
},
|
|
12756
12838
|
body: JSON.stringify({
|
|
12757
12839
|
db: currentDbInfo.id,
|
|
12840
|
+
...currentSchema ? { schema: currentSchema } : {},
|
|
12758
12841
|
sql,
|
|
12759
12842
|
saveHistory: true,
|
|
12760
12843
|
source: "browser",
|
|
@@ -12769,11 +12852,13 @@ ${frontmatter.yaml}
|
|
|
12769
12852
|
historyView.refresh();
|
|
12770
12853
|
return result;
|
|
12771
12854
|
}
|
|
12772
|
-
async function selectDb(dbId, explorerInitial, generation = loadGeneration, preferredTable) {
|
|
12855
|
+
async function selectDb(dbId, explorerInitial, generation = loadGeneration, preferredSchema, preferredTable) {
|
|
12773
12856
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
12774
12857
|
return;
|
|
12775
12858
|
currentTable = null;
|
|
12776
12859
|
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch") {
|
|
12860
|
+
currentSchema = null;
|
|
12861
|
+
renderSchemaOptions([], null);
|
|
12777
12862
|
currentTab = "data";
|
|
12778
12863
|
tableList.render([]);
|
|
12779
12864
|
grid.clear();
|
|
@@ -12799,6 +12884,34 @@ ${frontmatter.yaml}
|
|
|
12799
12884
|
tableList.render([]);
|
|
12800
12885
|
setTableListStatus("Loading schema...");
|
|
12801
12886
|
grid.clear();
|
|
12887
|
+
if (isPostgresKind(currentDbInfo?.kind)) {
|
|
12888
|
+
const desiredSchema = preferredSchema !== undefined ? preferredSchema : currentSchema;
|
|
12889
|
+
const schemas = await fetchSchemas(dbId, desiredSchema).catch((err) => {
|
|
12890
|
+
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
12891
|
+
return;
|
|
12892
|
+
const message = errorMessage(err);
|
|
12893
|
+
currentSchema = null;
|
|
12894
|
+
renderSchemaOptions([], null);
|
|
12895
|
+
schemaCache = null;
|
|
12896
|
+
tableList.render([]);
|
|
12897
|
+
setTableListStatus(message, { error: true });
|
|
12898
|
+
grid.showError(message);
|
|
12899
|
+
schemaView.clear();
|
|
12900
|
+
erDiagram.clear();
|
|
12901
|
+
cb.onStateChange();
|
|
12902
|
+
return null;
|
|
12903
|
+
});
|
|
12904
|
+
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
12905
|
+
return;
|
|
12906
|
+
if (!schemas)
|
|
12907
|
+
return;
|
|
12908
|
+
const schemaNames = schemas.schemas.map((s2) => s2.name);
|
|
12909
|
+
currentSchema = schemas.selectedSchema || desiredSchema || (schemaNames.includes("public") ? "public" : schemaNames[0]) || null;
|
|
12910
|
+
renderSchemaOptions(schemaNames, currentSchema);
|
|
12911
|
+
} else {
|
|
12912
|
+
currentSchema = null;
|
|
12913
|
+
renderSchemaOptions([], null);
|
|
12914
|
+
}
|
|
12802
12915
|
const schema = await fetchSchema(dbId).catch((err) => {
|
|
12803
12916
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
12804
12917
|
return;
|
|
@@ -12817,6 +12930,7 @@ ${frontmatter.yaml}
|
|
|
12817
12930
|
return;
|
|
12818
12931
|
if (!schema)
|
|
12819
12932
|
return;
|
|
12933
|
+
currentSchema = schema.schema || currentSchema;
|
|
12820
12934
|
schemaCache = schema;
|
|
12821
12935
|
tableList.render(schema.tables);
|
|
12822
12936
|
grid.clear();
|
|
@@ -12844,6 +12958,7 @@ ${frontmatter.yaml}
|
|
|
12844
12958
|
deps.setRoute({
|
|
12845
12959
|
screen: "database",
|
|
12846
12960
|
db: currentDbInfo.id,
|
|
12961
|
+
schema: currentSchema ?? undefined,
|
|
12847
12962
|
table: table2,
|
|
12848
12963
|
tab: currentTab === "data" ? undefined : currentTab,
|
|
12849
12964
|
range: deps.currentRange()
|
|
@@ -12874,7 +12989,7 @@ ${frontmatter.yaml}
|
|
|
12874
12989
|
}
|
|
12875
12990
|
if (!currentDbInfo)
|
|
12876
12991
|
return [];
|
|
12877
|
-
const res = await fetch(`/_db/columns
|
|
12992
|
+
const res = await fetch(`/_db/columns?${withCurrentSchema(new URLSearchParams({ db: currentDbInfo.id, table: table2 }))}`);
|
|
12878
12993
|
if (!res.ok)
|
|
12879
12994
|
return [];
|
|
12880
12995
|
const data = await res.json();
|
|
@@ -12892,7 +13007,7 @@ ${frontmatter.yaml}
|
|
|
12892
13007
|
return;
|
|
12893
13008
|
setActiveTab("schema");
|
|
12894
13009
|
try {
|
|
12895
|
-
const res = await fetch(`/_db/ddl
|
|
13010
|
+
const res = await fetch(`/_db/ddl?${withCurrentSchema(new URLSearchParams({ db: currentDbInfo.id, table: table2 }))}`);
|
|
12896
13011
|
if (!res.ok)
|
|
12897
13012
|
return;
|
|
12898
13013
|
const data = await res.json();
|
|
@@ -12925,6 +13040,33 @@ ${frontmatter.yaml}
|
|
|
12925
13040
|
dbSelect.addEventListener("change", () => {
|
|
12926
13041
|
handleDbSelectChange();
|
|
12927
13042
|
});
|
|
13043
|
+
schemaSelect.addEventListener("change", () => {
|
|
13044
|
+
handleSchemaSelectChange();
|
|
13045
|
+
});
|
|
13046
|
+
async function handleSchemaSelectChange() {
|
|
13047
|
+
if (!currentDbInfo || !isPostgresKind(currentDbInfo.kind))
|
|
13048
|
+
return;
|
|
13049
|
+
const schema = schemaSelect.value || null;
|
|
13050
|
+
if (!schema || schema === currentSchema)
|
|
13051
|
+
return;
|
|
13052
|
+
const dbId = currentDbInfo.id;
|
|
13053
|
+
currentSchema = schema;
|
|
13054
|
+
const generation = ++loadGeneration;
|
|
13055
|
+
clearDockerNotice();
|
|
13056
|
+
await selectDb(dbId, undefined, generation, schema, null);
|
|
13057
|
+
if (generation !== loadGeneration || currentDbInfo?.id !== dbId || currentSchema !== schema) {
|
|
13058
|
+
return;
|
|
13059
|
+
}
|
|
13060
|
+
deps.setRoute({
|
|
13061
|
+
screen: "database",
|
|
13062
|
+
db: dbId,
|
|
13063
|
+
schema,
|
|
13064
|
+
table: currentTable ?? undefined,
|
|
13065
|
+
tab: currentTab === "data" ? undefined : currentTab,
|
|
13066
|
+
range: deps.currentRange()
|
|
13067
|
+
}, true);
|
|
13068
|
+
cb.onStateChange();
|
|
13069
|
+
}
|
|
12928
13070
|
async function handleDbSelectChange() {
|
|
12929
13071
|
const dbId = dbSelect.value;
|
|
12930
13072
|
if (!dbId)
|
|
@@ -12939,13 +13081,15 @@ ${frontmatter.yaml}
|
|
|
12939
13081
|
sizeBytes: file?.sizeBytes || 0,
|
|
12940
13082
|
kind: file?.kind || "sqlite"
|
|
12941
13083
|
};
|
|
13084
|
+
currentSchema = null;
|
|
12942
13085
|
clearDockerNotice();
|
|
12943
|
-
await selectDb(dbId, undefined, generation);
|
|
13086
|
+
await selectDb(dbId, undefined, generation, null);
|
|
12944
13087
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
12945
13088
|
return;
|
|
12946
13089
|
deps.setRoute({
|
|
12947
13090
|
screen: "database",
|
|
12948
13091
|
db: dbId,
|
|
13092
|
+
schema: currentSchema ?? undefined,
|
|
12949
13093
|
table: currentTable ?? undefined,
|
|
12950
13094
|
tab: currentTab === "data" ? undefined : currentTab,
|
|
12951
13095
|
range: deps.currentRange()
|
|
@@ -12954,7 +13098,7 @@ ${frontmatter.yaml}
|
|
|
12954
13098
|
}
|
|
12955
13099
|
let pendingRedisInitial = initial.redis;
|
|
12956
13100
|
let pendingEsInitial = initial.es;
|
|
12957
|
-
async function enter(db, table2, view, options = {}) {
|
|
13101
|
+
async function enter(db, schema, table2, view, options = {}) {
|
|
12958
13102
|
const generation = ++loadGeneration;
|
|
12959
13103
|
const filesResponse = await fetchDbFiles();
|
|
12960
13104
|
if (generation !== loadGeneration)
|
|
@@ -12992,8 +13136,10 @@ ${frontmatter.yaml}
|
|
|
12992
13136
|
if (!db && !autoSelectFirst) {
|
|
12993
13137
|
dbSelect.value = "";
|
|
12994
13138
|
currentDbInfo = null;
|
|
13139
|
+
currentSchema = null;
|
|
12995
13140
|
currentTable = null;
|
|
12996
13141
|
schemaCache = null;
|
|
13142
|
+
renderSchemaOptions([], null);
|
|
12997
13143
|
tableList.render([]);
|
|
12998
13144
|
grid.clear();
|
|
12999
13145
|
schemaView.clear();
|
|
@@ -13013,7 +13159,7 @@ ${frontmatter.yaml}
|
|
|
13013
13159
|
};
|
|
13014
13160
|
pendingRedisInitial = undefined;
|
|
13015
13161
|
pendingEsInitial = undefined;
|
|
13016
|
-
await selectDb(target, explorerInitial, generation, table2);
|
|
13162
|
+
await selectDb(target, explorerInitial, generation, schema !== undefined ? schema : currentSchema, table2);
|
|
13017
13163
|
if (generation !== loadGeneration || currentDbInfo?.id !== target)
|
|
13018
13164
|
return;
|
|
13019
13165
|
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch") {
|
|
@@ -13046,6 +13192,8 @@ ${frontmatter.yaml}
|
|
|
13046
13192
|
function applyAnnotationTarget(target) {
|
|
13047
13193
|
if (!target || target.db !== currentDbInfo?.id)
|
|
13048
13194
|
return;
|
|
13195
|
+
if (target.schema && target.schema !== currentSchema)
|
|
13196
|
+
return;
|
|
13049
13197
|
if (target.data) {
|
|
13050
13198
|
if (target.table && target.table !== currentTable)
|
|
13051
13199
|
return;
|
|
@@ -13082,6 +13230,7 @@ ${frontmatter.yaml}
|
|
|
13082
13230
|
const state = {
|
|
13083
13231
|
id: cb.tabId,
|
|
13084
13232
|
dbId: currentDbInfo?.id ?? null,
|
|
13233
|
+
schema: currentSchema,
|
|
13085
13234
|
table: currentTable ?? activeTable?.dataset.table ?? null,
|
|
13086
13235
|
view: currentTab
|
|
13087
13236
|
};
|
|
@@ -13113,6 +13262,7 @@ ${frontmatter.yaml}
|
|
|
13113
13262
|
const target = {
|
|
13114
13263
|
kind: "database",
|
|
13115
13264
|
db: currentDbInfo.id,
|
|
13265
|
+
...currentSchema ? { schema: currentSchema } : {},
|
|
13116
13266
|
...currentTable ? { table: currentTable } : {},
|
|
13117
13267
|
tab: currentTab
|
|
13118
13268
|
};
|
|
@@ -13129,12 +13279,14 @@ ${frontmatter.yaml}
|
|
|
13129
13279
|
function getLabel() {
|
|
13130
13280
|
if (!currentDbInfo)
|
|
13131
13281
|
return labelFromDbId(initial.dbId);
|
|
13282
|
+
const suffix = currentSchema ? ` / ${currentSchema}` : "";
|
|
13132
13283
|
if (currentDbInfo.id.startsWith("docker:")) {
|
|
13133
13284
|
const m = currentDbInfo.name.match(/^(\S+)/);
|
|
13134
|
-
return m ? m[1] : currentDbInfo.name
|
|
13285
|
+
return `${m ? m[1] : currentDbInfo.name}${suffix}`;
|
|
13135
13286
|
}
|
|
13136
13287
|
const lastSlash = currentDbInfo.path.lastIndexOf("/");
|
|
13137
|
-
|
|
13288
|
+
const label = lastSlash >= 0 ? currentDbInfo.path.slice(lastSlash + 1) : currentDbInfo.path;
|
|
13289
|
+
return `${label}${suffix}`;
|
|
13138
13290
|
}
|
|
13139
13291
|
function dispose() {
|
|
13140
13292
|
loadGeneration++;
|
|
@@ -13149,6 +13301,7 @@ ${frontmatter.yaml}
|
|
|
13149
13301
|
redisExplorer.dispose();
|
|
13150
13302
|
esExplorer.dispose();
|
|
13151
13303
|
currentDbInfo = null;
|
|
13304
|
+
currentSchema = null;
|
|
13152
13305
|
currentTable = null;
|
|
13153
13306
|
schemaCache = null;
|
|
13154
13307
|
}
|
|
@@ -13294,6 +13447,7 @@ ${frontmatter.yaml}
|
|
|
13294
13447
|
deps.setRoute({
|
|
13295
13448
|
screen: "database",
|
|
13296
13449
|
db: s2.dbId ?? undefined,
|
|
13450
|
+
schema: s2.schema ?? undefined,
|
|
13297
13451
|
table: s2.table ?? undefined,
|
|
13298
13452
|
tab: s2.view === "data" ? undefined : s2.view,
|
|
13299
13453
|
range: deps.currentRange()
|
|
@@ -13314,6 +13468,7 @@ ${frontmatter.yaml}
|
|
|
13314
13468
|
for (const tab of tabs) {
|
|
13315
13469
|
const key = JSON.stringify({
|
|
13316
13470
|
dbId: tab.dbId,
|
|
13471
|
+
schema: tab.schema ?? null,
|
|
13317
13472
|
table: tab.table,
|
|
13318
13473
|
view: tab.view,
|
|
13319
13474
|
redis: tab.redis ?? null,
|
|
@@ -13544,6 +13699,7 @@ ${frontmatter.yaml}
|
|
|
13544
13699
|
}
|
|
13545
13700
|
}, {
|
|
13546
13701
|
dbId: initial?.dbId ?? null,
|
|
13702
|
+
schema: initial?.schema ?? null,
|
|
13547
13703
|
table: initial?.table ?? null,
|
|
13548
13704
|
view: initial?.view ?? undefined,
|
|
13549
13705
|
sqlDraft: initial?.sqlDraft,
|
|
@@ -13572,7 +13728,7 @@ ${frontmatter.yaml}
|
|
|
13572
13728
|
if (options.annotationTarget)
|
|
13573
13729
|
restoring = true;
|
|
13574
13730
|
try {
|
|
13575
|
-
await pane.enter(initial?.dbId || undefined, initial?.table || undefined, initial?.view || undefined, options);
|
|
13731
|
+
await pane.enter(initial?.dbId || undefined, initial?.schema || undefined, initial?.table || undefined, initial?.view || undefined, options);
|
|
13576
13732
|
refreshChipLabel(id);
|
|
13577
13733
|
} finally {
|
|
13578
13734
|
if (options.annotationTarget)
|
|
@@ -13609,35 +13765,41 @@ ${frontmatter.yaml}
|
|
|
13609
13765
|
newTabBtn.addEventListener("click", () => {
|
|
13610
13766
|
openTab({ dbId: null, table: null, view: "data" }, { autoSelectFirst: false });
|
|
13611
13767
|
});
|
|
13612
|
-
function routeMatchesState(state, db, table2, view) {
|
|
13613
|
-
if (!db && (table2 || view) && !state.dbId)
|
|
13768
|
+
function routeMatchesState(state, db, schema, table2, view) {
|
|
13769
|
+
if (!db && (schema || table2 || view) && !state.dbId)
|
|
13614
13770
|
return false;
|
|
13615
13771
|
if (db !== undefined && state.dbId !== db)
|
|
13616
13772
|
return false;
|
|
13773
|
+
if (schema !== undefined) {
|
|
13774
|
+
const routeSchema = schema || "public";
|
|
13775
|
+
const stateSchema = state.schema || "public";
|
|
13776
|
+
if (stateSchema !== routeSchema)
|
|
13777
|
+
return false;
|
|
13778
|
+
}
|
|
13617
13779
|
if (table2 !== undefined && state.table !== table2)
|
|
13618
13780
|
return false;
|
|
13619
13781
|
if (view !== undefined && state.view !== view)
|
|
13620
13782
|
return false;
|
|
13621
13783
|
return true;
|
|
13622
13784
|
}
|
|
13623
|
-
async function applyRouteToTab(db, table2, view, options = {}) {
|
|
13624
|
-
async function enterPane(pane, targetDb, targetTable, targetView, enterOptions) {
|
|
13785
|
+
async function applyRouteToTab(db, schema, table2, view, options = {}) {
|
|
13786
|
+
async function enterPane(pane, targetDb, targetSchema, targetTable, targetView, enterOptions) {
|
|
13625
13787
|
if (!enterOptions?.annotationTarget) {
|
|
13626
|
-
await pane.enter(targetDb, targetTable, targetView, enterOptions);
|
|
13788
|
+
await pane.enter(targetDb, targetSchema, targetTable, targetView, enterOptions);
|
|
13627
13789
|
return;
|
|
13628
13790
|
}
|
|
13629
13791
|
restoring = true;
|
|
13630
13792
|
try {
|
|
13631
|
-
await pane.enter(targetDb, targetTable, targetView, enterOptions);
|
|
13793
|
+
await pane.enter(targetDb, targetSchema, targetTable, targetView, enterOptions);
|
|
13632
13794
|
} finally {
|
|
13633
13795
|
restoring = false;
|
|
13634
13796
|
}
|
|
13635
13797
|
}
|
|
13636
13798
|
for (const [id2, entry] of tabsById) {
|
|
13637
|
-
if (routeMatchesState(entry.pane.getState(), db, table2, view)) {
|
|
13799
|
+
if (routeMatchesState(entry.pane.getState(), db, schema, table2, view)) {
|
|
13638
13800
|
setActive(id2);
|
|
13639
13801
|
if (options.annotationTarget) {
|
|
13640
|
-
await enterPane(entry.pane, db, table2, view, options);
|
|
13802
|
+
await enterPane(entry.pane, db, schema, table2, view, options);
|
|
13641
13803
|
if (!mounted)
|
|
13642
13804
|
return;
|
|
13643
13805
|
}
|
|
@@ -13649,7 +13811,7 @@ ${frontmatter.yaml}
|
|
|
13649
13811
|
const targetId = activeTabId;
|
|
13650
13812
|
const active = tabsById.get(activeTabId);
|
|
13651
13813
|
if (active) {
|
|
13652
|
-
await enterPane(active.pane, db, table2, view, options);
|
|
13814
|
+
await enterPane(active.pane, db, schema, table2, view, options);
|
|
13653
13815
|
if (!mounted)
|
|
13654
13816
|
return;
|
|
13655
13817
|
refreshChipLabel(targetId);
|
|
@@ -13658,6 +13820,7 @@ ${frontmatter.yaml}
|
|
|
13658
13820
|
}
|
|
13659
13821
|
const id = openTab({
|
|
13660
13822
|
dbId: db ?? null,
|
|
13823
|
+
schema: schema ?? null,
|
|
13661
13824
|
table: table2 ?? null,
|
|
13662
13825
|
view: view ?? "data"
|
|
13663
13826
|
}, { autoSelectFirst: db === undefined, ...options });
|
|
@@ -13676,7 +13839,17 @@ ${frontmatter.yaml}
|
|
|
13676
13839
|
return null;
|
|
13677
13840
|
}
|
|
13678
13841
|
}
|
|
13679
|
-
|
|
13842
|
+
function parseSseSchema(data) {
|
|
13843
|
+
if (!data)
|
|
13844
|
+
return null;
|
|
13845
|
+
try {
|
|
13846
|
+
const parsed = JSON.parse(data);
|
|
13847
|
+
return typeof parsed.schema === "string" ? parsed.schema : null;
|
|
13848
|
+
} catch {
|
|
13849
|
+
return null;
|
|
13850
|
+
}
|
|
13851
|
+
}
|
|
13852
|
+
async function doEnter(seq, db, schema, table2, view, options = {}) {
|
|
13680
13853
|
if (seq !== lifecycleSeq)
|
|
13681
13854
|
return;
|
|
13682
13855
|
const firstMount = !mounted;
|
|
@@ -13739,8 +13912,8 @@ ${frontmatter.yaml}
|
|
|
13739
13912
|
} finally {
|
|
13740
13913
|
restoring = false;
|
|
13741
13914
|
}
|
|
13742
|
-
if (db || table2 || view) {
|
|
13743
|
-
await applyRouteToTab(db, table2, view, {
|
|
13915
|
+
if (db || schema || table2 || view) {
|
|
13916
|
+
await applyRouteToTab(db, schema ?? null, table2, view, {
|
|
13744
13917
|
...options,
|
|
13745
13918
|
reuseActiveTab: options.reuseActiveTab ?? true
|
|
13746
13919
|
});
|
|
@@ -13765,6 +13938,7 @@ ${frontmatter.yaml}
|
|
|
13765
13938
|
if (tabsById.size === 0) {
|
|
13766
13939
|
const id = openTab({
|
|
13767
13940
|
dbId: db || null,
|
|
13941
|
+
schema: schema || null,
|
|
13768
13942
|
table: table2 || null,
|
|
13769
13943
|
view: view || "data"
|
|
13770
13944
|
}, { autoSelectFirst: !db, ...options });
|
|
@@ -13780,8 +13954,8 @@ ${frontmatter.yaml}
|
|
|
13780
13954
|
const active = tabsById.get(activeTabId);
|
|
13781
13955
|
if (!active)
|
|
13782
13956
|
return;
|
|
13783
|
-
if (db || table2 || view) {
|
|
13784
|
-
await applyRouteToTab(db, table2, view, {
|
|
13957
|
+
if (db || schema || table2 || view) {
|
|
13958
|
+
await applyRouteToTab(db, schema ?? null, table2, view, {
|
|
13785
13959
|
...options,
|
|
13786
13960
|
reuseActiveTab: options.reuseActiveTab ?? true
|
|
13787
13961
|
});
|
|
@@ -13789,9 +13963,9 @@ ${frontmatter.yaml}
|
|
|
13789
13963
|
syncActiveRoute();
|
|
13790
13964
|
}
|
|
13791
13965
|
}
|
|
13792
|
-
async function enter(db, table2, view, options = {}) {
|
|
13966
|
+
async function enter(db, schema, table2, view, options = {}) {
|
|
13793
13967
|
const seq = lifecycleSeq;
|
|
13794
|
-
enterQueue = enterQueue.catch(() => {}).then(() => doEnter(seq, db, table2, view, options));
|
|
13968
|
+
enterQueue = enterQueue.catch(() => {}).then(() => doEnter(seq, db, schema, table2, view, options));
|
|
13795
13969
|
await enterQueue;
|
|
13796
13970
|
}
|
|
13797
13971
|
function leave() {
|
|
@@ -13841,8 +14015,12 @@ ${frontmatter.yaml}
|
|
|
13841
14015
|
if (!mounted)
|
|
13842
14016
|
return;
|
|
13843
14017
|
const dbId = parseSseDbId(data);
|
|
14018
|
+
const schema = parseSseSchema(data);
|
|
13844
14019
|
for (const [, entry] of tabsById) {
|
|
13845
|
-
|
|
14020
|
+
const state = entry.pane.getState();
|
|
14021
|
+
if (dbId && state.dbId !== dbId)
|
|
14022
|
+
continue;
|
|
14023
|
+
if (schema && state.schema !== schema)
|
|
13846
14024
|
continue;
|
|
13847
14025
|
entry.pane.handleSse(event, data);
|
|
13848
14026
|
}
|
|
@@ -23784,7 +23962,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
23784
23962
|
return Promise.resolve(null);
|
|
23785
23963
|
}
|
|
23786
23964
|
if (STATE.route.screen === "database") {
|
|
23787
|
-
DATABASE_VIEW.enter(STATE.route.db, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
23965
|
+
DATABASE_VIEW.enter(STATE.route.db, STATE.route.schema, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
23788
23966
|
setStatus("live");
|
|
23789
23967
|
return Promise.resolve(null);
|
|
23790
23968
|
}
|
|
@@ -23855,7 +24033,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
23855
24033
|
HISTORY_VIEW.enterHistory();
|
|
23856
24034
|
} else if (STATE.route.screen === "database") {
|
|
23857
24035
|
setStatus("live");
|
|
23858
|
-
DATABASE_VIEW.enter(STATE.route.db, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
24036
|
+
DATABASE_VIEW.enter(STATE.route.db, STATE.route.schema, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
23859
24037
|
} else
|
|
23860
24038
|
load();
|
|
23861
24039
|
syncLineRefPill();
|
|
@@ -24005,7 +24183,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
24005
24183
|
cancelActiveSourceLoad("navigation");
|
|
24006
24184
|
setPageMode();
|
|
24007
24185
|
removeStandaloneSource();
|
|
24008
|
-
DATABASE_VIEW.enter(STATE.route.db, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
24186
|
+
DATABASE_VIEW.enter(STATE.route.db, STATE.route.schema, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
24009
24187
|
setStatus("live");
|
|
24010
24188
|
return;
|
|
24011
24189
|
}
|
|
@@ -24123,7 +24301,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
24123
24301
|
},
|
|
24124
24302
|
openDatabaseAnnotation: (target) => {
|
|
24125
24303
|
setStatus("live");
|
|
24126
|
-
return DATABASE_VIEW.enter(target.db, target.table, target.tab, {
|
|
24304
|
+
return DATABASE_VIEW.enter(target.db, target.schema, target.table, target.tab, {
|
|
24127
24305
|
annotationTarget: target,
|
|
24128
24306
|
reuseActiveTab: true
|
|
24129
24307
|
});
|