name the tables nothing on this machine owns
The marketplace screen now lists tables in the database that no code here claims, with a row estimate and, where the prefix says so, which absent plugin they look like they belong to. This is the information that cost an evening. `bun db:push` saw tables the schema did not declare, asked an interactive question no non-TTY caller could answer, applied nothing and exited 0 — and the install reported success while the plugin was unusable. The cause was two tables from before an extraction, found by reading `\dt` by hand. Nothing in the platform would ever have mentioned them. ── What counts as an owner ── The core schema, or a plugin DIRECTORY — not a plugin INSTALL. A plugin that is present but uninstalled still owns its tables, because that is the promise uninstall makes: keep every row so a reinstall puts you back where you were. So this reports only what no code on this machine describes at all, which on a healthy install is nothing, and the block does not render. A plugin whose schema will not import has its prefix treated as claimed rather than having every one of its tables reported as unowned — a broken manifest should not read as data with no home. ── No delete button, deliberately ── Dropping a table is not something to offer beside a list of plugins, and the right action differs per row: `music_favorites` wants the music plugin installed, `headscale_servers` wants dropping. Naming them is the part that was missing. Verified by planting `headscale_servers` and `music_playlists_old` and reading them back through the same call the route makes: the first attributed to nothing, the second to music, which is not on this machine. Row counts come from `pg_class.reltuples` — an exact count on a large table is not worth blocking a page render for a number that only answers "is there anything in here". tsgo clean, frontend builds, 873 pass / 7 fail unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { mountPrefix } from '../../plugins/manifest';
|
||||
import { snapshotPlugins } from '../../plugins/mount';
|
||||
import { manualInstallHint, reportDependencies } from '../../plugins/os-deps';
|
||||
import { fetchCatalogue, fetchReadme } from '../../plugins/marketplace';
|
||||
import { findOrphanTables } from '../../plugins/orphans';
|
||||
import { iconUrl } from '../../app-store/assets';
|
||||
import {
|
||||
installPlugin,
|
||||
@@ -83,7 +84,16 @@ pluginsRouter.get('/', async (ctx) => {
|
||||
const onDisk = new Set(states.map((s) => s.plugin.appName));
|
||||
const catalogue = (await fetchCatalogue().catch(() => [])).filter((e) => !onDisk.has(e.appName));
|
||||
|
||||
// Tables nothing on this machine claims. Cheap — one pg_class query and one import per plugin — and
|
||||
// computed here rather than behind its own route so the screen cannot show a plugin list without it.
|
||||
// Never fatal: a database that will not answer this must not take the plugins page down with it.
|
||||
const orphans = await findOrphanTables(catalogue.map((e) => e.appName)).catch((err) => {
|
||||
console.error('[plugins] orphan scan failed —', err instanceof Error ? err.message : err);
|
||||
return [];
|
||||
});
|
||||
|
||||
return ctx.json({
|
||||
orphans,
|
||||
available: catalogue.map((e) => ({
|
||||
appName: e.appName,
|
||||
prefix: e.publisher === 'officerdev' ? `/${e.appName}` : `/p/${e.publisher}/${e.appName}`,
|
||||
|
||||
Reference in New Issue
Block a user