refactor: tighten TypeScript boundaries

This commit is contained in:
ajnart committed 2026-08-24 09:38:30 +02:00
1 parent ad0120fe01
commit 24e6e4a47d
38 files changed
+246 -190

No files matched your search

+5 -4
View File
@@ -47,10 +47,11 @@ const getProcedureInput = (inputs: z.ZodType[]) => {
};
const normalizeJsonSchema = (schema: z.core.JSONSchema.JSONSchema): z.core.JSONSchema.JSONSchema => {
const properties = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;
const required = ((schema.required as string[] | undefined) ?? []).filter(
(key) => !("default" in (properties[key] ?? {})),
);
const properties = schema.properties ?? {};
const required = (schema.required ?? []).filter((key) => {
const property = properties[key];
return !property || typeof property !== "object" || !("default" in property);
});
return {
...schema,
+36 -20
View File
@@ -32,6 +32,14 @@ interface IntegrationAccessMetadata {
allowedIntegrationKinds: readonly IntegrationKind[];
}
const throwUnexpectedIntegrationKind = (kind: IntegrationKind, allowedKinds: readonly IntegrationKind[]): never => {
logger.error("Database returned an integration outside the requested kinds", {
integrationKind: kind,
allowedIntegrationKinds: allowedKinds,
});
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Integration kind validation failed" });
};
const createOneIntegrationMiddlewareForKinds = <TKind extends IntegrationKind>(
action: IntegrationAction,
kinds: readonly TKind[],
@@ -75,21 +83,19 @@ const createOneIntegrationMiddlewareForKinds = <TKind extends IntegrationKind>(
resolvedIntegrationKinds: [integration.kind],
});
const {
secrets,
kind,
items: _ignore1,
groupPermissions: _ignore2,
userPermissions: _ignore3,
...rest
} = integration;
const narrowedKind = kinds.find((kind) => kind === integration.kind);
if (!narrowedKind) {
return throwUnexpectedIntegrationKind(integration.kind, kinds);
}
const { secrets, items: _ignore1, groupPermissions: _ignore2, userPermissions: _ignore3, ...rest } = integration;
return next({
ctx: {
integration: {
...rest,
externalUrl: rest.app?.href ?? null,
kind: kind as TKind,
kind: narrowedKind,
decryptedSecrets: secrets.map((secret) => ({
...secret,
value: decryptSecret(secret.value),
@@ -176,15 +182,19 @@ const createManyIntegrationMiddlewareForKinds = <TKind extends IntegrationKind>(
return next({
ctx: {
integrations: dbIntegrations.map(
({ secrets, kind, items: _ignore1, groupPermissions: _ignore2, userPermissions: _ignore3, ...rest }) => ({
...rest,
externalUrl: rest.app?.href ?? null,
kind: kind as TKind,
decryptedSecrets: secrets.map((secret) => ({
...secret,
value: decryptSecret(secret.value),
})),
}),
({ secrets, kind, items: _ignore1, groupPermissions: _ignore2, userPermissions: _ignore3, ...rest }) => {
const narrowedKind = kinds.find((allowedKind) => allowedKind === kind);
if (!narrowedKind) return throwUnexpectedIntegrationKind(kind, kinds);
return {
...rest,
externalUrl: rest.app?.href ?? null,
kind: narrowedKind,
decryptedSecrets: secrets.map((secret) => ({
...secret,
value: decryptSecret(secret.value),
})),
};
},
),
},
});
@@ -212,13 +222,19 @@ export const createOneWidgetIntegrationMiddleware = <TWidgetKind extends WidgetK
action: IntegrationAction,
widgetKind: TWidgetKind,
): ReturnType<typeof createOneIntegrationMiddlewareForKinds<WidgetIntegrationKind<TWidgetKind>>> =>
createOneIntegrationMiddlewareForKinds(action, getWidgetIntegrationConfig(widgetKind).supportedIntegrations);
createOneIntegrationMiddlewareForKinds<WidgetIntegrationKind<TWidgetKind>>(
action,
getWidgetIntegrationConfig(widgetKind).supportedIntegrations,
);
export const createManyWidgetIntegrationMiddleware = <TWidgetKind extends WidgetKindWithIntegration>(
action: IntegrationAction,
widgetKind: TWidgetKind,
): ReturnType<typeof createManyIntegrationMiddlewareForKinds<WidgetIntegrationKind<TWidgetKind>>> =>
createManyIntegrationMiddlewareForKinds(action, getWidgetIntegrationConfig(widgetKind).supportedIntegrations);
createManyIntegrationMiddlewareForKinds<WidgetIntegrationKind<TWidgetKind>>(
action,
getWidgetIntegrationConfig(widgetKind).supportedIntegrations,
);
/**
* Throws a TRPCError FORBIDDEN if the user does not have permission to perform the specified action on at least one of the specified integrations
+1 -1
View File
@@ -43,7 +43,7 @@ export const calendarRouter = createTRPCRouter({
name: integration.name,
kind: integration.kind,
},
error: undefined as string | undefined,
error: undefined,
};
},
{
+24 -11
View File
@@ -11,23 +11,36 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
const firewallMiddleware = createManyWidgetIntegrationMiddleware("query", "firewall");
const queryFirewall = <
THandler extends {
handler: (integration: any, input: any) => { getDataAsync: () => Promise<{ data: any; timestamp: Date }> };
},
>(
handler: THandler,
createFallback: () => Awaited<ReturnType<ReturnType<THandler["handler"]>["getDataAsync"]>>["data"],
) =>
type FirewallIntegration = Parameters<typeof firewallCpuRequestHandler.handler>[0];
interface FirewallHandler<TData> {
handler: (
integration: FirewallIntegration,
input: Record<string, never>,
) => { getDataAsync: () => Promise<{ data: TData; timestamp: Date }> };
}
interface FirewallQueryResult<TData> {
integration: {
id: string;
name: string;
kind: FirewallIntegration["kind"];
updatedAt: Date;
};
summary: TData;
error: string | undefined;
}
const queryFirewall = <TData>(handler: FirewallHandler<TData>, createFallback: () => TData) =>
publicProcedure.concat(firewallMiddleware).query(async ({ ctx }) =>
settleIntegrationQueries(
ctx.integrations,
async (integration) => {
async (integration): Promise<FirewallQueryResult<TData>> => {
const { data, timestamp } = await handler.handler(integration, {}).getDataAsync();
return {
integration: { id: integration.id, name: integration.name, kind: integration.kind, updatedAt: timestamp },
summary: data as Awaited<ReturnType<ReturnType<THandler["handler"]>["getDataAsync"]>>["data"],
error: undefined as string | undefined,
summary: data,
error: undefined,
};
},
{
@@ -21,7 +21,7 @@ export const indexerManagerRouter = createTRPCRouter({
integrationId: integration.id,
integrationName: integration.name,
indexers,
error: undefined as string | undefined,
error: undefined,
};
},
{
@@ -27,7 +27,7 @@ export const mediaOrganizerRouter = createTRPCRouter({
integrationId: integration.id,
integrationName: integration.name,
...data,
error: undefined as string | undefined,
error: undefined,
};
},
{
@@ -42,7 +42,7 @@ export const mediaRequestsRouter = createTRPCRouter({
return {
integration: { id: integration.id, name: integration.name, kind: integration.kind },
data,
error: undefined as string | undefined,
error: undefined,
};
},
{
@@ -98,8 +98,8 @@ export const mediaRequestsRouter = createTRPCRouter({
const { data } = await mediaRequestStatsRequestHandler.handler(integration, {}).getDataAsync();
return {
integration: { id: integration.id, name: integration.name, kind: integration.kind },
data: { ...data, stats: data.stats as typeof data.stats | null },
error: undefined as string | undefined,
data,
error: undefined,
};
},
{
@@ -29,7 +29,7 @@ export const mediaServerRouter = createTRPCRouter({
integrationName: integration.name,
integrationKind: integration.kind,
sessions: data,
error: undefined as string | undefined,
error: undefined,
};
},
{
@@ -22,7 +22,7 @@ export const notificationsRouter = createTRPCRouter({
updatedAt: timestamp,
},
data,
error: undefined as string | undefined,
error: undefined,
};
},
{
@@ -18,9 +18,9 @@ export const speedtestTrackerRouter = createTRPCRouter({
integrationId: integration.id,
integrationName: integration.name,
integrationUrl: integration.url,
dashboard: data as typeof data | null,
dashboard: data,
updatedAt: timestamp,
error: undefined as string | undefined,
error: undefined,
};
},
{
+2 -2
View File
@@ -18,9 +18,9 @@ export const tracearrRouter = createTRPCRouter({
integrationId: integration.id,
integrationName: integration.name,
integrationUrl: integration.url,
dashboard: data as typeof data | null,
dashboard: data,
updatedAt: timestamp,
error: undefined as string | undefined,
error: undefined,
};
},
{
+2 -2
View File
@@ -25,9 +25,9 @@ export const traefikRouter = createTRPCRouter({
integrationId: integration.id,
integrationName: integration.name,
integrationUrl: integration.url,
dashboard: data as typeof data | null,
dashboard: data,
updatedAt: timestamp,
error: undefined as string | undefined,
error: undefined,
};
},
{
+1 -1
View File
@@ -18,7 +18,7 @@ export const upsRouter = createTRPCRouter({
integrationUrl: integration.url,
summaries: data,
updatedAt: timestamp,
error: undefined as string | undefined,
error: undefined,
};
},
{
@@ -18,9 +18,9 @@ export const uptimeKumaRouter = createTRPCRouter({
integrationId: integration.id,
integrationName: integration.name,
integrationUrl: integration.url,
dashboard: data as typeof data | null,
dashboard: data,
updatedAt: timestamp,
error: undefined as string | undefined,
error: undefined,
};
},
{
+2 -2
View File
@@ -19,8 +19,8 @@ export const vpnRouter = createTRPCRouter({
kind: integration.kind,
updatedAt: timestamp,
},
summary: data as typeof data | null,
error: undefined as string | undefined,
summary: data,
error: undefined,
};
},
{
+6 -6
View File
@@ -15,18 +15,18 @@ interface IntegrationLike {
kind: string;
}
interface Options<TIntegration extends IntegrationLike, TResult> {
fallback?: (integration: TIntegration, error: unknown) => TResult;
interface Options<TIntegration extends IntegrationLike, TFallback> {
fallback?: (integration: TIntegration, error: unknown) => TFallback;
throwOnAllFailures?: boolean;
}
export async function settleIntegrationQueries<TIntegration extends IntegrationLike, TResult>(
export async function settleIntegrationQueries<TIntegration extends IntegrationLike, TResult, TFallback = TResult>(
integrations: TIntegration[],
fn: (integration: TIntegration) => Promise<TResult>,
options?: Options<TIntegration, TResult>,
): Promise<TResult[]> {
options?: Options<TIntegration, TFallback>,
): Promise<(TResult | TFallback)[]> {
const settled = await Promise.allSettled(integrations.map(async (integration) => fn(integration)));
const results: TResult[] = [];
const results: (TResult | TFallback)[] = [];
const errors: unknown[] = [];
settled.forEach((result, index) => {