mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 22:55:24 +00:00
feat: give plugins their own tables, migrations and sync entities
This commit is contained in:
1 parent
5796186a1c
commit
c496a093ca
47 files changed
+32595
-290
No files matched your search
@@ -34,6 +34,7 @@ import { OpenTabRepository } from "./open-tab-repository.js";
|
||||
import { OpksshTokenRepository } from "./opkssh-token-repository.js";
|
||||
import { PluginRepository } from "./plugin-repository.js";
|
||||
import { PluginStorageRepository } from "./plugin-storage-repository.js";
|
||||
import { PluginMigrationRepository } from "./plugin-migration-repository.js";
|
||||
import { PluginPermissionGrantRepository } from "./plugin-permission-grant-repository.js";
|
||||
import { PluginRegistryRepository } from "./plugin-registry-repository.js";
|
||||
import { PluginInstallCountRepository } from "./plugin-install-count-repository.js";
|
||||
@@ -393,6 +394,13 @@ export function createCurrentPluginStorageRepository(): PluginStorageRepository
|
||||
);
|
||||
}
|
||||
|
||||
export function createCurrentPluginMigrationRepository(): PluginMigrationRepository {
|
||||
return new PluginMigrationRepository(
|
||||
createCurrentRepositoryContext(),
|
||||
createCurrentRepositoryWriteHook("plugin_migration_repository_write"),
|
||||
);
|
||||
}
|
||||
|
||||
export function createCurrentPluginRegistryRepository(): PluginRegistryRepository {
|
||||
return new PluginRegistryRepository(
|
||||
createCurrentRepositoryContext(),
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { pluginMigrations } from "../db/schema.js";
|
||||
import { insertReturning } from "./returning.js";
|
||||
import { rowsAffected } from "./mutation-result.js";
|
||||
import type { DatabaseContext } from "./database-context.js";
|
||||
|
||||
export type PluginMigrationRecord = typeof pluginMigrations.$inferSelect;
|
||||
|
||||
/**
|
||||
* Which of a plugin's migrations have run.
|
||||
*
|
||||
* The checksum is the reason this is a table rather than a file marker: it
|
||||
* makes an applied migration immutable, so an edited one is caught instead of
|
||||
* leaving two installs with quietly different schemas.
|
||||
*/
|
||||
export class PluginMigrationRepository {
|
||||
constructor(
|
||||
private readonly context: DatabaseContext,
|
||||
private readonly onWrite?: () => void | Promise<void>,
|
||||
) {}
|
||||
|
||||
async listByPlugin(pluginId: string): Promise<PluginMigrationRecord[]> {
|
||||
return this.context.drizzle
|
||||
.select()
|
||||
.from(pluginMigrations)
|
||||
.where(eq(pluginMigrations.pluginId, pluginId));
|
||||
}
|
||||
|
||||
async record(
|
||||
pluginId: string,
|
||||
migrationId: string,
|
||||
checksum: string,
|
||||
now = new Date().toISOString(),
|
||||
): Promise<void> {
|
||||
await insertReturning(this.context, pluginMigrations, {
|
||||
pluginId,
|
||||
migrationId,
|
||||
checksum,
|
||||
appliedAt: now,
|
||||
});
|
||||
|
||||
await this.afterWrite();
|
||||
}
|
||||
|
||||
async deleteByPlugin(pluginId: string): Promise<number> {
|
||||
const result = await this.context.drizzle
|
||||
.delete(pluginMigrations)
|
||||
.where(eq(pluginMigrations.pluginId, pluginId));
|
||||
|
||||
const affected = rowsAffected(result);
|
||||
if (affected > 0) await this.afterWrite();
|
||||
return affected;
|
||||
}
|
||||
|
||||
async has(pluginId: string, migrationId: string): Promise<boolean> {
|
||||
const rows = await this.context.drizzle
|
||||
.select()
|
||||
.from(pluginMigrations)
|
||||
.where(
|
||||
and(
|
||||
eq(pluginMigrations.pluginId, pluginId),
|
||||
eq(pluginMigrations.migrationId, migrationId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
private async afterWrite(): Promise<void> {
|
||||
await this.onWrite?.();
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,16 @@ export class PluginStorageRepository {
|
||||
return rows.map((row) => row.key);
|
||||
}
|
||||
|
||||
/** How many keys this plugin holds, for the per-plugin key cap. */
|
||||
async countKeys(pluginId: string): Promise<number> {
|
||||
const rows = await this.context.drizzle
|
||||
.select()
|
||||
.from(pluginStorage)
|
||||
.where(eq(pluginStorage.pluginId, pluginId));
|
||||
|
||||
return rows.length;
|
||||
}
|
||||
|
||||
async set(
|
||||
pluginId: string,
|
||||
key: string,
|
||||
|
||||
@@ -5,17 +5,16 @@ import type { DatabaseContext } from "./database-context.js";
|
||||
|
||||
export type SyncTombstoneRecord = typeof syncTombstones.$inferSelect;
|
||||
|
||||
export type SyncEntityType =
|
||||
| "hosts"
|
||||
| "sshCredentials"
|
||||
| "sshFolders"
|
||||
| "snippets"
|
||||
| "snippetFolders"
|
||||
| "vaultProfiles"
|
||||
| "dashboardServiceLinks"
|
||||
| "homepageItems"
|
||||
| "userPreferences"
|
||||
| "networkTopology";
|
||||
/**
|
||||
* The wire name of a synced entity.
|
||||
*
|
||||
* A string rather than a union: the set is whatever core and the installed
|
||||
* plugins have registered, so it cannot be known at compile time. The column
|
||||
* was already free-form text with no enum constraint, so nothing in the
|
||||
* database changes. sync.ts validates a name through the registry before it
|
||||
* reaches a query.
|
||||
*/
|
||||
export type SyncEntityType = string;
|
||||
|
||||
export class SyncTombstoneRepository {
|
||||
constructor(
|
||||
|
||||
Reference in new issue
Block a user