feat(xugu): expose programmable object metadata

This commit is contained in:
Elias authored and GitHub committed 2026-07-17 17:53:44 +08:00
1 parent d3887c9936
commit 08cd8903f4
36 files changed
+431 -62

No files matched your search

@@ -5,7 +5,7 @@ export type ObjectBrowserRowAction = "table-info" | "open-table" | "open-source"
/**
* Determine the action for a single click on an object browser row.
* - TABLE → table-info (show table properties panel)
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/SEQUENCE/PACKAGE/PACKAGE_BODY → open-source
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/TRIGGER/SEQUENCE/PACKAGE/PACKAGE_BODY/TYPE/TYPE_BODY → open-source
* - otherwise → none
*/
export function singleClickRowAction(row: ObjectBrowserRow | null | undefined): ObjectBrowserRowAction {
@@ -18,7 +18,7 @@ export function singleClickRowAction(row: ObjectBrowserRow | null | undefined):
/**
* Determine the action for a double click on an object browser row.
* - TABLE → open-table (open table data tab)
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/SEQUENCE/PACKAGE/PACKAGE_BODY → open-source
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/TRIGGER/SEQUENCE/PACKAGE/PACKAGE_BODY/TYPE/TYPE_BODY → open-source
* - otherwise → none
*/
export function doubleClickRowAction(row: ObjectBrowserRow | null | undefined): ObjectBrowserRowAction {
@@ -62,6 +62,14 @@ export function shouldDeferSingleClick(row: ObjectBrowserRow | null | undefined,
return single !== double && action === single;
}
/**
* Objects with source metadata but no supported object-browser mutation API.
* Their menu intentionally exposes only source viewing and copying.
*/
export function isSourceOnlyObjectBrowserRow(row: ObjectBrowserRow): boolean {
return row.type === "TRIGGER" || row.type === "SEQUENCE" || row.type === "PACKAGE" || row.type === "PACKAGE_BODY" || row.type === "TYPE" || row.type === "TYPE_BODY";
}
function canOpenSource(row: ObjectBrowserRow): boolean {
return row.type === "VIEW" || row.type === "MATERIALIZED_VIEW" || row.type === "PROCEDURE" || row.type === "FUNCTION" || row.type === "SEQUENCE" || row.type === "PACKAGE" || row.type === "PACKAGE_BODY";
return row.type === "VIEW" || row.type === "MATERIALIZED_VIEW" || row.type === "PROCEDURE" || row.type === "FUNCTION" || row.type === "TRIGGER" || row.type === "SEQUENCE" || row.type === "PACKAGE" || row.type === "PACKAGE_BODY" || row.type === "TYPE" || row.type === "TYPE_BODY";
}
@@ -7,7 +7,8 @@ export type ObjectBrowserRow = {
name: string;
displayName: string;
schema?: string;
type: "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
type: "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "TRIGGER" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY" | "TYPE" | "TYPE_BODY";
valid?: boolean | null;
signature?: string | null;
comment?: string | null;
created_at?: string | null;
@@ -27,7 +28,10 @@ export function normalizeObjectBrowserType(type: string): ObjectBrowserRow["type
const value = type.toUpperCase();
const normalized = value.replace(/[\s-]+/g, "_");
if (normalized.includes("PACKAGE_BODY")) return "PACKAGE_BODY";
if (normalized.includes("TYPE_BODY")) return "TYPE_BODY";
if (normalized.includes("PACKAGE")) return "PACKAGE";
if (normalized.includes("TRIGGER")) return "TRIGGER";
if (normalized.includes("TYPE")) return "TYPE";
if (normalized.includes("MATERIALIZED_VIEW")) return "MATERIALIZED_VIEW";
if (value.includes("VIEW")) return "VIEW";
if (value.includes("SEQ")) return "SEQUENCE";
@@ -57,6 +61,7 @@ export function buildObjectBrowserRows(options: { objects: ObjectInfo[]; databas
displayName,
schema,
type,
valid: object.valid,
signature,
comment: object.comment,
created_at: object.created_at,
+22 -3
View File
@@ -504,7 +504,7 @@ export function buildSimpleObjectTreeNodes({ nodeId, connectionId, database, sch
for (const obj of objects) {
const objectType = normalizeObjectType(obj.object_type);
if (!["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"].includes(objectType)) {
if (!["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "TRIGGER", "SEQUENCE", "PACKAGE", "PACKAGE_BODY", "TYPE", "TYPE_BODY"].includes(objectType)) {
continue;
}
@@ -540,6 +540,7 @@ export function buildSimpleObjectTreeNodes({ nodeId, connectionId, database, sch
objectName: name,
signature: signature || undefined,
comment: obj.comment,
valid: obj.valid ?? undefined,
connectionId,
database,
schema: childSchema,
@@ -557,9 +558,12 @@ function simpleObjectNodeType(objectType: DatabaseObjectTreeKind): TreeNodeType
if (objectType === "MATERIALIZED_VIEW") return "materialized_view";
if (objectType === "PROCEDURE") return "procedure";
if (objectType === "FUNCTION") return "function";
if (objectType === "TRIGGER") return "trigger";
if (objectType === "SEQUENCE") return "sequence";
if (objectType === "PACKAGE_BODY") return "package-body";
if (objectType === "PACKAGE") return "package";
if (objectType === "TYPE_BODY") return "type-body";
if (objectType === "TYPE") return "type";
return "table";
}
@@ -597,6 +601,13 @@ const groupDefs: Array<{
nodeType: "group-functions",
childType: "function",
},
{
key: "__triggers",
label: "tree.triggers",
objectTypes: ["TRIGGER"],
nodeType: "group-triggers",
childType: "trigger",
},
{
key: "__sequences",
label: "tree.sequences",
@@ -611,9 +622,16 @@ const groupDefs: Array<{
nodeType: "group-packages",
childType: (objectType) => (objectType === "PACKAGE_BODY" ? "package-body" : "package"),
},
{
key: "__types",
label: "tree.types",
objectTypes: ["TYPE", "TYPE_BODY"],
nodeType: "group-types",
childType: (objectType) => (objectType === "TYPE_BODY" ? "type-body" : "type"),
},
];
const objectGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages"]);
const objectGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-packages", "group-types"]);
export function buildObjectGroupPlaceholderNodes({ nodeId, connectionId, database, schema, objectTypes }: { nodeId: string; connectionId: string; database: string; schema?: string; objectTypes: DatabaseObjectTreeKind[] }): TreeNode[] {
const supported = new Set(objectTypes);
@@ -676,12 +694,13 @@ export function buildGroupedObjectTreeNodes({ nodeId, connectionId, database, sc
const childSchema = obj.schema ? normalizeDatabaseObjectName(obj.schema) : schema;
const objectType = normalizeObjectType(obj.object_type);
const childType = typeof def.childType === "function" ? def.childType(objectType) : def.childType;
const objectTypeSuffix = objectType === "PACKAGE" || objectType === "PACKAGE_BODY" ? `:${objectType}` : "";
const objectTypeSuffix = objectType === "PACKAGE" || objectType === "PACKAGE_BODY" || objectType === "TYPE" || objectType === "TYPE_BODY" ? `:${objectType}` : "";
return {
id: `${nodeId}:${def.key}:${childSchema ? `${childSchema}:` : ""}${obj.name}${objectTypeSuffix}`,
label: obj.name,
type: childType,
comment: obj.comment,
valid: obj.valid ?? undefined,
connectionId,
database,
schema: childSchema,