Files
mb-grid-solutions.com/lib/services/create-services.ts
Marc Mintel d0d66dd85f
Some checks failed
Build & Deploy / 🔍 Prepare Environment (push) Successful in 5s
Build & Deploy / 🧪 QA (push) Failing after 1m14s
Build & Deploy / 🏗️ Build (push) Failing after 4m44s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🔔 Notifications (push) Successful in 1s
fix: linting
2026-02-07 01:25:15 +01:00

155 lines
5.3 KiB
TypeScript

import { AppServices } from "./app-services";
import { NoopAnalyticsService } from "./analytics/noop-analytics-service";
import { MemoryCacheService } from "./cache/memory-cache-service";
import { GlitchtipErrorReportingService } from "./errors/glitchtip-error-reporting-service";
import { NoopErrorReportingService } from "./errors/noop-error-reporting-service";
import { NoopLoggerService } from "./logging/noop-logger-service";
import { PinoLoggerService } from "./logging/pino-logger-service";
import { NoopNotificationService } from "./notifications/gotify-notification-service";
import { config, getMaskedConfig } from "../config";
/**
* Singleton instance of AppServices.
*
* In Next.js, module singletons are per-process (server) and per-tab (client).
* This is sufficient for a small service layer and provides better performance
* than creating new instances on every request.
*
* @private
*/
let singleton: AppServices | undefined;
/**
* Get the application services singleton.
*
* This function creates and caches the application services, including:
* - Analytics service (Umami or no-op)
* - Error reporting service (GlitchTip/Sentry or no-op)
* - Cache service (in-memory)
*
* The services are configured based on environment variables:
* - `UMAMI_WEBSITE_ID` - Enables Umami analytics
* - `NEXT_PUBLIC_SENTRY_DSN` - Enables client-side error reporting
* - `SENTRY_DSN` - Enables server-side error reporting
*
* @returns {AppServices} The application services singleton
*
* @example
* ```typescript
* // Get services in a client component
* import { getAppServices } from '@/lib/services/create-services';
*
* const services = getAppServices();
* services.analytics.track('button_click', { button_id: 'cta' });
* ```
*
* @example
* ```typescript
* // Get services in a server component or API route
* import { getAppServices } from '@/lib/services/create-services';
*
* const services = getAppServices();
* await services.cache.set('key', 'value');
* ```
*
* @example
* ```typescript
* // Automatic service selection based on environment
* // If NEXT_PUBLIC_UMAMI_WEBSITE_ID is set:
* // services.analytics = UmamiAnalyticsService
* // If not set:
* // services.analytics = NoopAnalyticsService (safe no-op)
* ```
*
* @see {@link UmamiAnalyticsService} for analytics implementation
* @see {@link NoopAnalyticsService} for no-op fallback
* @see {@link GlitchtipErrorReportingService} for error reporting
* @see {@link MemoryCacheService} for caching
*/
export function getAppServices(): AppServices {
// Return cached instance if available
if (singleton) return singleton;
// Create logger first to log initialization
const logger =
typeof window === "undefined"
? new PinoLoggerService("server")
: new NoopLoggerService();
// Log initialization
if (typeof window === "undefined") {
// Server-side
logger.info("Initializing server application services", {
environment: getMaskedConfig(),
timestamp: new Date().toISOString(),
});
} else {
// Client-side
logger.info("Initializing client application services", {
environment: getMaskedConfig(),
timestamp: new Date().toISOString(),
});
}
// Determine which services to enable based on environment variables
const umamiEnabled = config.analytics.umami.enabled;
const sentryEnabled = config.errors.glitchtip.enabled;
logger.info("Service configuration", {
umamiEnabled,
sentryEnabled,
isServer: typeof window === "undefined",
});
// Create analytics service (Umami or no-op)
// Use dynamic import to avoid importing server-only code in client components
const analytics = umamiEnabled
? (() => {
const { UmamiAnalyticsService } =
// eslint-disable-next-line @typescript-eslint/no-require-imports
require("./analytics/umami-analytics-service");
return new UmamiAnalyticsService({ enabled: true });
})()
: new NoopAnalyticsService();
if (umamiEnabled) {
logger.info("Umami analytics service initialized");
} else {
logger.info("Noop analytics service initialized (analytics disabled)");
}
// Create error reporting service (GlitchTip/Sentry or no-op)
const errors = sentryEnabled
? new GlitchtipErrorReportingService({ enabled: true })
: new NoopErrorReportingService();
if (sentryEnabled) {
logger.info(
`GlitchTip error reporting service initialized (${typeof window === "undefined" ? "server" : "client"})`,
);
} else {
logger.info(
"Noop error reporting service initialized (error reporting disabled)",
);
}
// IMPORTANT: This module is imported by client components.
// Do not import Node-only modules (like the `redis` client) here.
// Use [`getServerAppServices()`](lib/services/create-services.server.ts:1) on the server.
const cache = new MemoryCacheService();
logger.info("Memory cache service initialized");
logger.info("Pino logger service initialized", {
name: typeof window === "undefined" ? "server" : "client",
level: config.logging.level,
});
// Create and cache the singleton
const notifications = new NoopNotificationService();
singleton = new AppServices(analytics, errors, cache, logger, notifications);
logger.info("All application services initialized successfully");
return singleton;
}