Registry API Refactoring (#144)

* Refactor core to use a flexible registry pattern

- Introduce a new registry mechanism for mailer providers
- Extract mailer provider enum to a separate file
- Implement dynamic mailer loading using a registry
- Update package dependencies and exports
- Improve modularity and extensibility of mailer implementation

* Refactor monitoring and billing services to use a flexible registry pattern

- Introduce a shared registry mechanism for dynamic service loading
- Replace static switch-based implementations with a registry-based approach
- Update instrumentation, CMS, and monitoring services to use the new registry
- Improve modularity and extensibility of service implementations
- Add Zod-based type-safe provider validation

* Simplify async registration in monitoring and billing services

- Remove unnecessary async wrappers for no-op registrations
- Update type definitions to support both async and sync registration functions
- Standardize registration approach for Paddle and Sentry providers

* Remove Tailwind package from packages where it is not being needed

* Remove Tailwind config references from pnpm-lock.yaml

* Update instrumentation registry to support dynamic monitoring providers

- Modify type definition to use NonNullable MonitoringProvider
- Import MonitoringProvider type from get-monitoring-provider
- Enhance type safety for instrumentation registration
This commit is contained in:
Giancarlo Buomprisco
2025-02-05 17:38:43 +07:00
committed by GitHub
parent 3140f0cf21
commit 4a47df81db
31 changed files with 414 additions and 287 deletions

View File

@@ -22,10 +22,11 @@
"@kit/monitoring-core": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/sentry": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/react": "19.0.8",
"react": "19.0.0"
"react": "19.0.0",
"zod": "^3.24.1"
},
"eslintConfig": {
"root": true,

View File

@@ -2,38 +2,64 @@
import { lazy } from 'react';
import { getMonitoringProvider } from '../get-monitoring-provider';
import { InstrumentationProvider } from '../monitoring-providers.enum';
import { createRegistry } from '@kit/shared/registry';
const BaselimeProvider = lazy(async () => {
import {
MonitoringProvider as MonitoringProviderType,
getMonitoringProvider,
} from '../get-monitoring-provider';
// Define the type for our provider components
type ProviderComponent = {
default: React.ComponentType<React.PropsWithChildren>;
};
// Create a registry for monitoring providers
const monitoringProviderRegistry = createRegistry<
ProviderComponent,
NonNullable<MonitoringProviderType>
>();
// Register the Baselime provider
monitoringProviderRegistry.register('baselime', async () => {
const { BaselimeProvider } = await import('@kit/baselime/provider');
return {
default: BaselimeProvider,
default: function BaselimeProviderWrapper({
children,
}: React.PropsWithChildren) {
return <BaselimeProvider enableWebVitals>{children}</BaselimeProvider>;
},
};
});
const SentryProvider = lazy(async () => {
// Register the Sentry provider
monitoringProviderRegistry.register('sentry', async () => {
const { SentryProvider } = await import('@kit/sentry/provider');
return {
default: SentryProvider,
default: function SentryProviderWrapper({
children,
}: React.PropsWithChildren) {
return <SentryProvider>{children}</SentryProvider>;
},
};
});
/**
* @name MonitoringProvider
* @description This component is used to wrap the application with the appropriate monitoring provider.
* @param props
* @returns
*/
export function MonitoringProvider(props: React.PropsWithChildren) {
const provider = getMonitoringProvider();
switch (provider) {
case InstrumentationProvider.Baselime:
return (
<BaselimeProvider enableWebVitals>{props.children}</BaselimeProvider>
);
case InstrumentationProvider.Sentry:
return <SentryProvider>{props.children}</SentryProvider>;
default:
return <>{props.children}</>;
if (!provider) {
return <>{props.children}</>;
}
const Provider = lazy(() => monitoringProviderRegistry.get(provider));
return <Provider>{props.children}</Provider>;
}

View File

@@ -1,7 +1,9 @@
import { InstrumentationProvider } from './monitoring-providers.enum';
import { z } from 'zod';
export const MONITORING_PROVIDER = z.enum(['baselime', 'sentry']).optional();
export type MonitoringProvider = z.infer<typeof MONITORING_PROVIDER>;
export function getMonitoringProvider() {
return process.env.NEXT_PUBLIC_MONITORING_PROVIDER as
| InstrumentationProvider
| undefined;
return MONITORING_PROVIDER.parse(process.env.NEXT_PUBLIC_MONITORING_PROVIDER);
}

View File

@@ -1,37 +1,51 @@
import { getMonitoringProvider } from './get-monitoring-provider';
import { InstrumentationProvider } from './monitoring-providers.enum';
import { createRegistry } from '@kit/shared/registry';
const PROVIDER = getMonitoringProvider();
import {
MonitoringProvider,
getMonitoringProvider,
} from './get-monitoring-provider';
// Define a type for the instrumentation registration implementation
type InstrumentationRegistration = {
register: () => Promise<void> | void;
};
// Create a registry for instrumentation providers, using literal strings 'baselime' and 'sentry'
const instrumentationRegistry = createRegistry<
InstrumentationRegistration,
NonNullable<MonitoringProvider>
>();
// Register the 'baselime' instrumentation provider
instrumentationRegistry.register('baselime', async () => {
const { registerInstrumentation } = await import(
'@kit/baselime/instrumentation'
);
return { register: registerInstrumentation };
});
// Register the 'sentry' instrumentation provider with a no-op registration, since Sentry v8 sets up automatically
instrumentationRegistry.register('sentry', () => {
return {
register: () => {
return;
},
};
});
/**
* @name registerMonitoringInstrumentation
* @description Register monitoring instrumentation based on the MONITORING_PROVIDER environment variable.
*
* Please set the MONITORING_PROVIDER environment variable to register the monitoring instrumentation provider.
* @description Register monitoring instrumentation based on the MONITORING_PROVIDER environment variable using the registry internally.
*/
export async function registerMonitoringInstrumentation() {
if (!PROVIDER) {
const provider = getMonitoringProvider();
if (!provider) {
return;
}
switch (PROVIDER) {
case InstrumentationProvider.Baselime: {
const { registerInstrumentation } = await import(
'@kit/baselime/instrumentation'
);
const instrumentation = await instrumentationRegistry.get(provider);
return registerInstrumentation();
}
case InstrumentationProvider.Sentry: {
// Sentry v8 automatically sets this up
return;
}
default:
throw new Error(
`Unknown instrumentation provider: ${process.env.NEXT_PUBLIC_MONITORING_PROVIDER}`,
);
}
return instrumentation.register();
}

View File

@@ -1,4 +0,0 @@
export enum InstrumentationProvider {
Baselime = 'baselime',
Sentry = 'sentry',
}

View File

@@ -1,42 +1,52 @@
import { ConsoleMonitoringService } from '@kit/monitoring-core';
import {
ConsoleMonitoringService,
MonitoringService,
} from '@kit/monitoring-core';
import { createRegistry } from '@kit/shared/registry';
import { getMonitoringProvider } from '../get-monitoring-provider';
import { InstrumentationProvider } from '../monitoring-providers.enum';
import {
MonitoringProvider,
getMonitoringProvider,
} from '../get-monitoring-provider';
const MONITORING_PROVIDER = getMonitoringProvider();
// create a registry for the server monitoring services
const serverMonitoringRegistry = createRegistry<
MonitoringService,
NonNullable<MonitoringProvider>
>();
// Register the 'baselime' monitoring service
serverMonitoringRegistry.register('baselime', async () => {
const { BaselimeServerMonitoringService } = await import(
'@kit/baselime/server'
);
return new BaselimeServerMonitoringService();
});
// Register the 'sentry' monitoring service
serverMonitoringRegistry.register('sentry', async () => {
const { SentryMonitoringService } = await import('@kit/sentry');
return new SentryMonitoringService();
});
// if you have a new monitoring provider, you can register it here
//
/**
* @name getServerMonitoringService
* @description Get the monitoring service based on the MONITORING_PROVIDER environment variable.
*/
export async function getServerMonitoringService() {
if (!MONITORING_PROVIDER) {
const provider = getMonitoringProvider();
if (!provider) {
console.info(
`No instrumentation provider specified. Returning console service...`,
);
return new ConsoleMonitoringService();
}
switch (MONITORING_PROVIDER) {
case InstrumentationProvider.Baselime: {
const { BaselimeServerMonitoringService } = await import(
'@kit/baselime/server'
);
return new BaselimeServerMonitoringService();
}
case InstrumentationProvider.Sentry: {
const { SentryMonitoringService } = await import('@kit/sentry');
return new SentryMonitoringService();
}
default: {
throw new Error(
`Please set the MONITORING_PROVIDER environment variable to register the monitoring instrumentation provider.`,
);
}
}
return serverMonitoringRegistry.get(provider);
}