Files
Termix/src/backend/database/repositories/plugin-install-count-repository.ts
T
62ac931468 v26.10.1 (#1599)
* feat: AI translations at release, 7 new languages and regional fallback

* fix: proper plural forms for count strings and full translations

* fix: plural forms for selected, hidden and active counts

* fix: synced plugin updates failing on desktop and server-only plugins shown as stopped

* fix: restore rounded corners on app icons and favicon

* feat: desktop app auto updates with automatic, ask first or never modes

* feat: community and custom plugin registries

* feat: plugin guide for AI tools in the SDK with a lint check

* fix: keep the terminal WebSocket open past nginx's 60s default (#1588)

* chore: update changelog

* feat: show plugin download counts only and stop CI builds inflating them

* feat: hide installed plugins in the browse tab by default

* chore: pin bundled plugins to latest releases

* chore: update translations

* fix: focusing a saved password field and saving no longer erases it

* fix: tell desktop users when their 2.8 data needs 2.9 first

* fix: point windows update info at the x64 installer

* fix: keep desktop client tunnel sockets alive behind proxies

* chore: pin bundled plugins to latest releases

* fix: cover client tunnel keepalive in the c2s runtime tests

* fix: typing after opening a host no longer goes to the host search

* fix: accept tab and = separators in SSH config import (#1598)

* chore: update changelog

* chore: drop plugin versions from core changelog

* chore: update translations

* chore: lint, format, and bump version to 26.10.1

---------

Co-authored-by: Darnel Hunter <68697450+innotelinc@users.noreply.github.com>
Co-authored-by: Iain <Iain1970@protonmail.com>
2026-10-12 11:35:48 -05:00

65 lines
1.9 KiB
TypeScript

import { and, eq } from "drizzle-orm";
import { pluginInstallCounts } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
export type PluginInstallCountRecord = typeof pluginInstallCounts.$inferSelect;
export interface PluginInstallCountInput {
pluginId: string;
count: number;
}
/** Download counts for the store, one row per plugin and registry. */
export class PluginInstallCountRepository {
constructor(
private readonly context: DatabaseContext,
private readonly onWrite?: () => void | Promise<void>,
) {}
async listByRegistry(
registryId: string,
): Promise<PluginInstallCountRecord[]> {
return this.context.drizzle
.select()
.from(pluginInstallCounts)
.where(eq(pluginInstallCounts.registryId, registryId));
}
/** Writes only the rows that changed, so an unchanged sync costs no save. */
async upsertMany(
registryId: string,
entries: PluginInstallCountInput[],
now = new Date().toISOString(),
): Promise<number> {
const existing = new Map(
(await this.listByRegistry(registryId)).map((row) => [row.pluginId, row]),
);
let written = 0;
for (const entry of entries) {
const row = existing.get(entry.pluginId);
if (row && row.count === entry.count) continue;
if (row) {
await this.context.drizzle
.update(pluginInstallCounts)
.set({ count: entry.count, updatedAt: now })
.where(
and(
eq(pluginInstallCounts.pluginId, entry.pluginId),
eq(pluginInstallCounts.registryId, registryId),
),
);
} else {
await this.context.drizzle.insert(pluginInstallCounts).values({
pluginId: entry.pluginId,
registryId,
count: entry.count,
updatedAt: now,
});
}
written += 1;
}
if (written > 0) await this.onWrite?.();
return written;
}
}