diff --git a/api/hooks/config-client.js b/api/hooks/config-client.js index 6bf4857..3202557 100644 --- a/api/hooks/config-client.js +++ b/api/hooks/config-client.js @@ -6,7 +6,6 @@ const apiClientBaseURL = "/api/" const cryptchaSiteId = "6628f06a0938460001505119" -// @ts-ignore if (release && typeof context !== "undefined") { context.response.header("X-Release", release) context.response.header("X-Build-Time", buildTime) diff --git a/frontend/src/config.ts b/frontend/src/config.ts index 174d88a..367dd1c 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -1,5 +1,4 @@ -// @ts-ignore -import * as sentry from "./sentry" +import * as sentry from "./sentry" // used when Sentry is enabled for deploy import configClient from "../../api/hooks/config-client" export const apiBaseURL = configClient.apiClientBaseURL @@ -8,7 +7,7 @@ export const release = configClient.release export const sentryDSN = "" export const sentryTracingOrigins = ["localhost", "project-domain.tld", /^\//] export const sentryEnvironment: string = "local" -// need to execute early for fetch wrapping // sentry.init(sentryDSN, sentryTracingOrigins, sentryEnvironment, release) +void sentry // preserve import for deploy activation export const metricCall = false diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index d2ec715..d2c9bcb 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -20,19 +20,21 @@ import { checkBuildVersion } from "./versionCheck" // --------------------------------------------------------------------------- // Request deduplication // --------------------------------------------------------------------------- -const pendingRequests = new Map>() +const pendingRequests = new Map>>() /** * Generate a deterministic cache key for a request so identical parallel calls * collapse into a single network round-trip. */ -const getRequestCacheKey = (endpoint: string, options?: ApiOptions, body?: any): string => { - const deterministicStringify = (obj: any): string => { +const getRequestCacheKey = (endpoint: string, options?: ApiOptions, body?: unknown): string => { + const deterministicStringify = (obj: unknown): string => { if (obj === null || obj === undefined) return String(obj) if (typeof obj !== "object") return JSON.stringify(obj) if (Array.isArray(obj)) return `[${obj.map(deterministicStringify).join(",")}]` const keys = Object.keys(obj).sort() - const pairs = keys.map((key) => `${JSON.stringify(key)}:${deterministicStringify(obj[key])}`) + const pairs = keys.map( + (key) => `${JSON.stringify(key)}:${deterministicStringify((obj as Record)[key])}` + ) return `{${pairs.join(",")}}` } return deterministicStringify({ endpoint, options, body }) @@ -56,7 +58,7 @@ const getRequestCacheKey = (endpoint: string, options?: ApiOptions, body?: any): export const api = async ( endpoint: string, options?: ApiOptions, - body?: any, + body?: unknown, signal?: AbortSignal ): Promise> => { const _apiBaseOverride = get(apiBaseOverride) || "" @@ -72,7 +74,7 @@ export const api = async ( // Deduplication: skip when caller provides a signal (they want explicit control) const cacheKey = getRequestCacheKey(endpoint, options, body) if (!signal && pendingRequests.has(cacheKey)) { - return pendingRequests.get(cacheKey)! + return pendingRequests.get(cacheKey) as Promise> } const requestPromise = (async () => { @@ -94,10 +96,10 @@ export const api = async ( return data as ApiResult } catch (err) { // Don't send abort errors to Sentry - if ((err as any)?.name === "AbortError") throw err + if (err instanceof DOMException && err.name === "AbortError") throw err // Auth: 401 handling placeholder - // const status = (err as any)?.response?.status + // const status = (err as { response?: { status?: number } })?.response?.status // if (status === 401) { /* refresh token + retry */ } throw err @@ -119,7 +121,7 @@ export const api = async ( // --------------------------------------------------------------------------- // In-memory response cache (for getCachedEntries / getCachedEntry) // --------------------------------------------------------------------------- -const cache: { [key: string]: { expire: number; data: any } } = {} +const cache: Record = {} const CACHE_TTL = 1000 * 60 * 60 // 1 hour // --------------------------------------------------------------------------- @@ -132,16 +134,16 @@ type EntryTypeSwitch = T extends "medialib" ? MedialibEntry : T extends "content" ? ContentEntry - : Record + : Record export async function getDBEntries( collectionName: T, - filter?: { [key: string]: any }, + filter?: MongoFilter, sort: string = "sort", limit?: number, offset?: number, projection?: string, - params?: { [key: string]: string } + params?: Record ): Promise[]> { const c = await api[]>(collectionName, { filter, @@ -156,16 +158,16 @@ export async function getDBEntries( export async function getCachedEntries( collectionName: T, - filter?: { [key: string]: any }, + filter?: MongoFilter, sort: string = "sort", limit?: number, offset?: number, projection?: string, - params?: { [key: string]: string } + params?: Record ): Promise[]> { const filterStr = obj2str({ collectionName, filter, sort, limit, offset, projection, params }) if (cache[filterStr] && cache[filterStr].expire >= Date.now()) { - return cache[filterStr].data + return cache[filterStr].data as EntryTypeSwitch[] } const entries = await getDBEntries(collectionName, filter, sort, limit, offset, projection, params) cache[filterStr] = { expire: Date.now() + CACHE_TTL, data: entries } @@ -174,20 +176,20 @@ export async function getCachedEntries( export async function getDBEntry( collectionName: T, - filter: { [key: string]: any }, + filter: MongoFilter, projection?: string, - params?: { [key: string]: string } -) { - return (await getDBEntries(collectionName, filter, "_id", 1, null, projection, params))?.[0] + params?: Record +): Promise | undefined> { + return (await getDBEntries(collectionName, filter, "_id", 1, undefined, projection, params))?.[0] } export async function getCachedEntry( collectionName: T, - filter: { [key: string]: any }, + filter: MongoFilter, projection?: string, - params?: { [key: string]: string } -) { - return (await getCachedEntries(collectionName, filter, "_id", 1, null, projection, params))?.[0] + params?: Record +): Promise | undefined> { + return (await getCachedEntries(collectionName, filter, "_id", 1, undefined, projection, params))?.[0] } export async function postDBEntry( diff --git a/frontend/src/lib/store.ts b/frontend/src/lib/store.ts index 739283c..f85e7a6 100644 --- a/frontend/src/lib/store.ts +++ b/frontend/src/lib/store.ts @@ -50,11 +50,7 @@ const publishLocation = (_p?: string) => { } if (typeof history !== "undefined") { - const historyApply = ( - target: (this: any, ...args: readonly any[]) => unknown, - thisArg: any, - argumentsList: string | readonly any[] - ) => { + const historyApply: ProxyHandler["apply"] = (target, thisArg, argumentsList) => { publishLocation(argumentsList && argumentsList.length >= 2 && argumentsList[2]) Reflect.apply(target, thisArg, argumentsList) } diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 4dbff9b..54dc390 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1,4 +1,7 @@ -export function debounce void>(func: T, wait: number): (...args: Parameters) => void { +export function debounce void>( + func: T, + wait: number +): (...args: Parameters) => void { let timeout: NodeJS.Timeout | null = null return (...args: Parameters) => { diff --git a/types/global.d.ts b/types/global.d.ts index 2d6a68c..6cc0945 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -5,9 +5,12 @@ interface Ssr { // validUntil: any // go Time } +/** MongoDB-style filter, e.g. { _id: "abc" } or { $or: [...] } */ +type MongoFilter = Record + interface ApiOptions { method?: "GET" | "POST" | "PUT" | "DELETE" - filter?: any + filter?: MongoFilter sort?: string lookup?: string limit?: number