Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/cache-bootstrap.server.ts
T

79 lines
3.5 KiB
TypeScript

import { createScopedLogger } from "./utils/logger.server";
import { subscribeSubscriptionCache } from "./utils/services/.server/subscriptionCache.server";
import { subscribeTenantCache } from "./utils/db/tenant-cache.server";
import { subscribeFeatureFlagCache } from "./utils/services/.server/featureFlagCache.server";
import { wireTenantPlanLookup } from "./utils/services/.server/tenantPlanLookup.server";
import { getSharedStore } from "./shared-store.server";
import { subscribePermissionCache } from "./utils/db/permissions/permission-cache.server";
import { subscribeApiKeyAuthCache } from "./utils/api/api-key-auth-cache.server";
import { subscribeUserSnapshotCache } from "./utils/db/user-snapshot-cache.server";
import { subscribeAppConfigurationCache } from "./utils/db/app-configuration-cache.server";
const log = createScopedLogger("cache-bootstrap");
let started = false;
function pubsubEnabled(): boolean {
const raw = process.env.CACHE_INVALIDATION_ENABLED;
if (raw === undefined || raw === null || raw === "") return true;
return raw.toLowerCase() !== "false" && raw !== "0";
}
/**
* Idempotent boot hook. Starts every cache-invalidation subscriber this
* web replica needs to keep its in-process L1 caches consistent with the
* SharedStore L2 (and with mutations originating in other apps such as
* the billing webhook in apps/api).
*
* Safe to call multiple times — only the first invocation wires the
* subscribers; subsequent calls are no-ops. Failures are logged but
* intentionally swallowed: the L1 cache TTL is the safety net.
*/
export function startCacheSubscribers(): void {
if (started) return;
// Resolve the backing store synchronously at boot. Production must never
// continue with replica-local cache and rate-limit state when Redis is
// missing; asynchronous subscriber errors would otherwise be swallowed.
getSharedStore();
started = true;
// Wire the BullMQ tenant-plan lookup unconditionally — it reads from the
// subscription cache which still serves locally when pub/sub is off.
wireTenantPlanLookup();
if (!pubsubEnabled()) {
log.warn(
{ component: "cache-bootstrap" },
"CACHE_INVALIDATION_ENABLED=false — pub/sub subscribers will not start (L1 TTL is the only consistency mechanism)"
);
return;
}
void Promise.all([
subscribeSubscriptionCache().catch((err: unknown) => {
log.error({ err }, "subscription cache subscriber failed to start");
}),
subscribeTenantCache().catch((err: unknown) => {
log.error({ err }, "tenant cache subscriber failed to start");
}),
subscribeFeatureFlagCache().catch((err: unknown) => {
log.error({ err }, "feature-flag cache subscriber failed to start");
}),
subscribePermissionCache().catch((err: unknown) => {
log.error({ err }, "permission cache subscriber failed to start");
}),
subscribeApiKeyAuthCache().catch((err: unknown) => {
log.error({ err }, "API-key authorization cache subscriber failed to start");
}),
subscribeUserSnapshotCache().catch((err: unknown) => {
log.error({ err }, "user snapshot cache subscriber failed to start");
}),
subscribeAppConfigurationCache().catch((err: unknown) => {
log.error({ err }, "app configuration cache subscriber failed to start");
}),
]).then(() => {
log.info({ component: "cache-bootstrap" }, "cache subscribers started");
});
}
/** Test seam: reset the started flag. */
export function __resetCacheBootstrapForTests(): void {
started = false;
}