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:
committed by
GitHub
parent
3140f0cf21
commit
4a47df81db
@@ -15,7 +15,6 @@
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/node": "^22.13.0"
|
||||
},
|
||||
|
||||
@@ -3,37 +3,44 @@ import 'server-only';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
BillingConfig,
|
||||
type BillingConfig,
|
||||
type BillingProviderSchema,
|
||||
BillingWebhookHandlerService,
|
||||
} from '@kit/billing';
|
||||
import { createRegistry } from '@kit/shared/registry';
|
||||
|
||||
export class BillingEventHandlerFactoryService {
|
||||
static async GetProviderStrategy(
|
||||
provider: z.infer<typeof BillingProviderSchema>,
|
||||
config: BillingConfig,
|
||||
): Promise<BillingWebhookHandlerService> {
|
||||
switch (provider) {
|
||||
case 'stripe': {
|
||||
const { StripeWebhookHandlerService } = await import('@kit/stripe');
|
||||
/**
|
||||
* @description Creates a registry for billing webhook handlers
|
||||
* @param config - The billing config
|
||||
* @returns The billing webhook handler registry
|
||||
*/
|
||||
export function createBillingEventHandlerFactoryService(config: BillingConfig) {
|
||||
// Create a registry for billing webhook handlers
|
||||
const billingWebhookHandlerRegistry = createRegistry<
|
||||
BillingWebhookHandlerService,
|
||||
z.infer<typeof BillingProviderSchema>
|
||||
>();
|
||||
|
||||
return new StripeWebhookHandlerService(config);
|
||||
}
|
||||
// Register the Stripe webhook handler
|
||||
billingWebhookHandlerRegistry.register('stripe', async () => {
|
||||
const { StripeWebhookHandlerService } = await import('@kit/stripe');
|
||||
|
||||
case 'lemon-squeezy': {
|
||||
const { LemonSqueezyWebhookHandlerService } = await import(
|
||||
'@kit/lemon-squeezy'
|
||||
);
|
||||
return new StripeWebhookHandlerService(config);
|
||||
});
|
||||
|
||||
return new LemonSqueezyWebhookHandlerService(config);
|
||||
}
|
||||
// Register the Lemon Squeezy webhook handler
|
||||
billingWebhookHandlerRegistry.register('lemon-squeezy', async () => {
|
||||
const { LemonSqueezyWebhookHandlerService } = await import(
|
||||
'@kit/lemon-squeezy'
|
||||
);
|
||||
|
||||
case 'paddle': {
|
||||
throw new Error('Paddle is not supported yet');
|
||||
}
|
||||
return new LemonSqueezyWebhookHandlerService(config);
|
||||
});
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported billing provider: ${provider as string}`);
|
||||
}
|
||||
}
|
||||
// Register Paddle webhook handler (not implemented yet)
|
||||
billingWebhookHandlerRegistry.register('paddle', () => {
|
||||
throw new Error('Paddle is not supported yet');
|
||||
});
|
||||
|
||||
return billingWebhookHandlerRegistry;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SupabaseClient } from '@supabase/supabase-js';
|
||||
import { BillingConfig } from '@kit/billing';
|
||||
import { Database, Enums } from '@kit/supabase/database';
|
||||
|
||||
import { BillingEventHandlerFactoryService } from './billing-event-handler-factory.service';
|
||||
import { createBillingEventHandlerFactoryService } from './billing-event-handler-factory.service';
|
||||
import { createBillingEventHandlerService } from './billing-event-handler.service';
|
||||
|
||||
// a function that returns a Supabase client
|
||||
@@ -25,10 +25,8 @@ export async function getBillingEventHandlerService(
|
||||
provider: BillingProvider,
|
||||
config: BillingConfig,
|
||||
) {
|
||||
const strategy = await BillingEventHandlerFactoryService.GetProviderStrategy(
|
||||
provider,
|
||||
config,
|
||||
);
|
||||
const strategy =
|
||||
await createBillingEventHandlerFactoryService(config).get(provider);
|
||||
|
||||
return createBillingEventHandlerService(clientProvider, strategy);
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'server-only';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
type BillingProviderSchema,
|
||||
BillingStrategyProviderService,
|
||||
} from '@kit/billing';
|
||||
|
||||
export class BillingGatewayFactoryService {
|
||||
static async GetProviderStrategy(
|
||||
provider: z.infer<typeof BillingProviderSchema>,
|
||||
): Promise<BillingStrategyProviderService> {
|
||||
switch (provider) {
|
||||
case 'stripe': {
|
||||
const { StripeBillingStrategyService } = await import('@kit/stripe');
|
||||
|
||||
return new StripeBillingStrategyService();
|
||||
}
|
||||
|
||||
case 'lemon-squeezy': {
|
||||
const { LemonSqueezyBillingStrategyService } = await import(
|
||||
'@kit/lemon-squeezy'
|
||||
);
|
||||
|
||||
return new LemonSqueezyBillingStrategyService();
|
||||
}
|
||||
|
||||
case 'paddle': {
|
||||
throw new Error('Paddle is not supported yet');
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported billing provider: ${provider as string}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'server-only';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
type BillingProviderSchema,
|
||||
BillingStrategyProviderService,
|
||||
} from '@kit/billing';
|
||||
import { createRegistry } from '@kit/shared/registry';
|
||||
|
||||
// Create a registry for billing strategy providers
|
||||
export const billingStrategyRegistry = createRegistry<
|
||||
BillingStrategyProviderService,
|
||||
z.infer<typeof BillingProviderSchema>
|
||||
>();
|
||||
|
||||
// Register the Stripe billing strategy
|
||||
billingStrategyRegistry.register('stripe', async () => {
|
||||
const { StripeBillingStrategyService } = await import('@kit/stripe');
|
||||
return new StripeBillingStrategyService();
|
||||
});
|
||||
|
||||
// Register the Lemon Squeezy billing strategy
|
||||
billingStrategyRegistry.register('lemon-squeezy', async () => {
|
||||
const { LemonSqueezyBillingStrategyService } = await import(
|
||||
'@kit/lemon-squeezy'
|
||||
);
|
||||
return new LemonSqueezyBillingStrategyService();
|
||||
});
|
||||
|
||||
// Register Paddle billing strategy (not implemented yet)
|
||||
billingStrategyRegistry.register('paddle', () => {
|
||||
throw new Error('Paddle is not supported yet');
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
UpdateSubscriptionParamsSchema,
|
||||
} from '@kit/billing/schema';
|
||||
|
||||
import { BillingGatewayFactoryService } from './billing-gateway-factory.service';
|
||||
import { billingStrategyRegistry } from './billing-gateway-registry';
|
||||
|
||||
export function createBillingGatewayService(
|
||||
provider: z.infer<typeof BillingProviderSchema>,
|
||||
@@ -138,6 +138,6 @@ class BillingGatewayService {
|
||||
}
|
||||
|
||||
private getStrategy() {
|
||||
return BillingGatewayFactoryService.GetProviderStrategy(this.provider);
|
||||
return billingStrategyRegistry.get(this.provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/keystatic": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/shared": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@kit/wordpress": "workspace:*",
|
||||
"@types/node": "^22.13.0"
|
||||
@@ -35,4 +36,4 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
import { CmsClient, CmsType } from '@kit/cms-types';
|
||||
import { createRegistry } from '@kit/shared/registry';
|
||||
|
||||
/**
|
||||
* The type of CMS client to use.
|
||||
*/
|
||||
const CMS_CLIENT = process.env.CMS_CLIENT as CmsType;
|
||||
|
||||
// Create a registry for CMS client implementations
|
||||
const cmsRegistry = createRegistry<CmsClient, CmsType>();
|
||||
|
||||
// Register the WordPress CMS client implementation
|
||||
cmsRegistry.register('wordpress', async () => {
|
||||
const { createWordpressClient } = await import('@kit/wordpress');
|
||||
return createWordpressClient();
|
||||
});
|
||||
|
||||
// Register the Keystatic CMS client implementation
|
||||
cmsRegistry.register('keystatic', async () => {
|
||||
const { createKeystaticClient } = await import('@kit/keystatic');
|
||||
return createKeystaticClient();
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a CMS client based on the specified type.
|
||||
*
|
||||
@@ -12,33 +28,6 @@ const CMS_CLIENT = process.env.CMS_CLIENT as CmsType;
|
||||
* @returns {Promise<CmsClient>} A Promise that resolves to the created CMS client.
|
||||
* @throws {Error} If the specified CMS type is unknown.
|
||||
*/
|
||||
export async function createCmsClient(
|
||||
type: CmsType = CMS_CLIENT,
|
||||
): Promise<CmsClient> {
|
||||
return cmsClientFactory(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CMS client based on the specified type.
|
||||
*
|
||||
* @param {CmsType} type - The type of CMS client to create.
|
||||
* @returns {Promise<CmsClient>} A Promise that resolves to the created CMS client.
|
||||
*/
|
||||
async function cmsClientFactory(type: CmsType): Promise<CmsClient> {
|
||||
switch (type) {
|
||||
case 'wordpress': {
|
||||
const { createWordpressClient } = await import('@kit/wordpress');
|
||||
|
||||
return createWordpressClient();
|
||||
}
|
||||
|
||||
case 'keystatic': {
|
||||
const { createKeystaticClient } = await import('@kit/keystatic');
|
||||
|
||||
return createKeystaticClient();
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown CMS type`);
|
||||
}
|
||||
export async function createCmsClient(type: CmsType = CMS_CLIENT) {
|
||||
return cmsRegistry.get(type);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"@kit/shared": "workspace:*",
|
||||
"@kit/stripe": "workspace:*",
|
||||
"@kit/supabase": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/team-accounts": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@supabase/supabase-js": "2.48.1",
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/shared": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@tanstack/react-query": "5.66.0",
|
||||
"next": "15.1.6",
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/mailers-shared": "workspace:*",
|
||||
"@kit/nodemailer": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/resend": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/shared": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/node": "^22.13.0",
|
||||
"zod": "^3.24.1"
|
||||
|
||||
@@ -1,37 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const MAILER_PROVIDER = z
|
||||
.enum(['nodemailer', 'resend'])
|
||||
.default('nodemailer')
|
||||
.parse(process.env.MAILER_PROVIDER);
|
||||
import { MAILER_PROVIDER } from './provider-enum';
|
||||
import { mailerRegistry } from './registry';
|
||||
|
||||
/**
|
||||
* @description Get the mailer based on the environment variable.
|
||||
* @name getMailer
|
||||
* @description Get the mailer based on the environment variable using the registry internally.
|
||||
*/
|
||||
export async function getMailer() {
|
||||
switch (MAILER_PROVIDER) {
|
||||
case 'nodemailer':
|
||||
return getNodemailer();
|
||||
|
||||
case 'resend': {
|
||||
const { createResendMailer } = await import('@kit/resend');
|
||||
|
||||
return createResendMailer();
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Invalid mailer: ${MAILER_PROVIDER as string}`);
|
||||
}
|
||||
export function getMailer() {
|
||||
return mailerRegistry.get(MAILER_PROVIDER);
|
||||
}
|
||||
|
||||
async function getNodemailer() {
|
||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||
const { createNodemailerService } = await import('@kit/nodemailer');
|
||||
|
||||
return createNodemailerService();
|
||||
} else {
|
||||
throw new Error(
|
||||
'Nodemailer is not available on the edge runtime. Please use another mailer.',
|
||||
);
|
||||
}
|
||||
}
|
||||
export { MAILER_PROVIDER };
|
||||
|
||||
16
packages/mailers/core/src/provider-enum.ts
Normal file
16
packages/mailers/core/src/provider-enum.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const MAILER_PROVIDERS = [
|
||||
'nodemailer',
|
||||
'resend',
|
||||
// add more providers here
|
||||
] as const;
|
||||
|
||||
const MAILER_PROVIDER = z
|
||||
.enum(MAILER_PROVIDERS)
|
||||
.default('nodemailer')
|
||||
.parse(process.env.MAILER_PROVIDER);
|
||||
|
||||
export { MAILER_PROVIDER };
|
||||
|
||||
export type MailerProvider = (typeof MAILER_PROVIDERS)[number];
|
||||
26
packages/mailers/core/src/registry.ts
Normal file
26
packages/mailers/core/src/registry.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Mailer } from '@kit/mailers-shared';
|
||||
import { createRegistry } from '@kit/shared/registry';
|
||||
|
||||
import { MailerProvider } from './provider-enum';
|
||||
|
||||
const mailerRegistry = createRegistry<Mailer, MailerProvider>();
|
||||
|
||||
mailerRegistry.register('nodemailer', async () => {
|
||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||
const { createNodemailerService } = await import('@kit/nodemailer');
|
||||
|
||||
return createNodemailerService();
|
||||
} else {
|
||||
throw new Error(
|
||||
'Nodemailer is not available on the edge runtime. Please use another mailer.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
mailerRegistry.register('resend', async () => {
|
||||
const { createResendMailer } = await import('@kit/resend');
|
||||
|
||||
return createResendMailer();
|
||||
});
|
||||
|
||||
export { mailerRegistry };
|
||||
@@ -19,7 +19,6 @@
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/mailers-shared": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/nodemailer": "6.4.17",
|
||||
"zod": "^3.24.1"
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/mailers-shared": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/node": "^22.13.0",
|
||||
"zod": "^3.24.1"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export enum InstrumentationProvider {
|
||||
Baselime = 'baselime',
|
||||
Sentry = 'sentry',
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/react": "19.0.8",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/react": "19.0.8",
|
||||
"react": "19.0.0"
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/monitoring-core": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/react": "19.0.8",
|
||||
"react": "19.0.0"
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"@kit/monitoring": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/supabase": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@supabase/supabase-js": "2.48.1",
|
||||
"next": "15.1.6",
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
"./logger": "./src/logger/index.ts",
|
||||
"./utils": "./src/utils.ts",
|
||||
"./hooks": "./src/hooks/index.ts",
|
||||
"./events": "./src/events/index.tsx"
|
||||
"./events": "./src/events/index.tsx",
|
||||
"./registry": "./src/registry/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tailwind-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/react": "19.0.8"
|
||||
},
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import { createRegistry } from '../registry';
|
||||
import { Logger as LoggerInstance } from './logger';
|
||||
|
||||
const LOGGER = process.env.LOGGER ?? 'pino';
|
||||
// Define the type for the logger provider. Currently supporting 'pino'.
|
||||
type LoggerProvider = 'pino';
|
||||
|
||||
/*
|
||||
* Logger
|
||||
* By default, the logger is set to use Pino. To change the logger, update the import statement below.
|
||||
* to your desired logger implementation.
|
||||
const LOGGER = (process.env.LOGGER ?? 'pino') as LoggerProvider;
|
||||
|
||||
// Create a registry for logger implementations
|
||||
const loggerRegistry = createRegistry<LoggerInstance, LoggerProvider>();
|
||||
|
||||
// Register the 'pino' logger implementation
|
||||
loggerRegistry.register('pino', async () => {
|
||||
const { Logger: PinoLogger } = await import('./impl/pino');
|
||||
|
||||
return PinoLogger;
|
||||
});
|
||||
|
||||
/**
|
||||
* @name getLogger
|
||||
* @description Retrieves the logger implementation based on the LOGGER environment variable using the registry API.
|
||||
*/
|
||||
async function getLogger(): Promise<LoggerInstance> {
|
||||
switch (LOGGER) {
|
||||
case 'pino': {
|
||||
const { Logger: PinoLogger } = await import('./impl/pino');
|
||||
|
||||
return PinoLogger;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown logger: ${process.env.LOGGER}`);
|
||||
}
|
||||
export async function getLogger() {
|
||||
return loggerRegistry.get(LOGGER);
|
||||
}
|
||||
|
||||
export { getLogger };
|
||||
|
||||
108
packages/shared/src/registry/index.ts
Normal file
108
packages/shared/src/registry/index.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Implementation factory type
|
||||
*/
|
||||
export type ImplementationFactory<T> = () =>
|
||||
| T
|
||||
| Promise<T>;
|
||||
|
||||
/**
|
||||
* Public API types with improved get method
|
||||
*/
|
||||
export interface Registry<T, Names extends string> {
|
||||
register: (
|
||||
name: Names,
|
||||
factory: ImplementationFactory<T>,
|
||||
) => Registry<T, Names>;
|
||||
|
||||
// Overloaded get method that infers return types based on input.
|
||||
get: {
|
||||
<K extends Names>(name: K): Promise<T>;
|
||||
<K extends [Names, ...Names[]]>(
|
||||
...names: K
|
||||
): Promise<{ [P in keyof K]: T }>;
|
||||
};
|
||||
|
||||
setup: (group?: string) => Promise<void>;
|
||||
addSetup: (
|
||||
group: string,
|
||||
callback: () => Promise<void>,
|
||||
) => Registry<T, Names>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name createRegistry
|
||||
* @description Creates a new registry instance with the provided implementations.
|
||||
* @returns A new registry instance.
|
||||
*/
|
||||
export function createRegistry<
|
||||
T,
|
||||
Names extends string = string,
|
||||
>(): Registry<T, Names> {
|
||||
const implementations = new Map<Names, ImplementationFactory<T>>();
|
||||
const setupCallbacks = new Map<string, Array<() => Promise<void>>>();
|
||||
const setupPromises = new Map<string, Promise<void>>();
|
||||
|
||||
const registry: Registry<T, Names> = {
|
||||
register(name, factory) {
|
||||
implementations.set(name, factory);
|
||||
return registry;
|
||||
},
|
||||
|
||||
// Updated get method overload that supports tuple inference
|
||||
get: (async (...names: Names[]) => {
|
||||
await registry.setup();
|
||||
|
||||
if (names.length === 1) {
|
||||
return await getImplementation(names[0]!);
|
||||
}
|
||||
|
||||
return await Promise.all(names.map((name) => getImplementation(name)));
|
||||
}) as Registry<T, Names>['get'],
|
||||
|
||||
async setup(group?: string) {
|
||||
if (group) {
|
||||
if (!setupPromises.has(group)) {
|
||||
const callbacks = setupCallbacks.get(group) ?? [];
|
||||
|
||||
setupPromises.set(
|
||||
group,
|
||||
Promise.all(callbacks.map((cb) => cb())).then(() => void 0),
|
||||
);
|
||||
}
|
||||
|
||||
return setupPromises.get(group);
|
||||
}
|
||||
|
||||
const groups = Array.from(setupCallbacks.keys());
|
||||
|
||||
await Promise.all(groups.map((group) => registry.setup(group)));
|
||||
},
|
||||
|
||||
addSetup(group, callback) {
|
||||
if (!setupCallbacks.has(group)) {
|
||||
setupCallbacks.set(group, []);
|
||||
}
|
||||
|
||||
setupCallbacks.get(group)!.push(callback);
|
||||
return registry;
|
||||
},
|
||||
};
|
||||
|
||||
async function getImplementation(name: Names) {
|
||||
const factory = implementations.get(name);
|
||||
|
||||
if (!factory) {
|
||||
throw new Error(`Implementation "${name}" not found`);
|
||||
}
|
||||
|
||||
const implementation = await factory();
|
||||
|
||||
if (!implementation) {
|
||||
throw new Error(`Implementation "${name}" is not available`);
|
||||
}
|
||||
|
||||
return implementation;
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
60
pnpm-lock.yaml
generated
60
pnpm-lock.yaml
generated
@@ -237,9 +237,6 @@ importers:
|
||||
'@kit/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/prettier
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/typescript
|
||||
@@ -443,6 +440,9 @@ importers:
|
||||
'@kit/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/prettier
|
||||
'@kit/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../../shared
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/typescript
|
||||
@@ -555,9 +555,6 @@ importers:
|
||||
'@kit/supabase':
|
||||
specifier: workspace:*
|
||||
version: link:../supabase
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/tailwind
|
||||
'@kit/team-accounts':
|
||||
specifier: workspace:*
|
||||
version: link:../features/team-accounts
|
||||
@@ -964,9 +961,6 @@ importers:
|
||||
'@kit/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/typescript
|
||||
@@ -991,6 +985,9 @@ importers:
|
||||
'@kit/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/eslint
|
||||
'@kit/mailers-shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
'@kit/nodemailer':
|
||||
specifier: workspace:*
|
||||
version: link:../nodemailer
|
||||
@@ -1000,9 +997,9 @@ importers:
|
||||
'@kit/resend':
|
||||
specifier: workspace:*
|
||||
version: link:../resend
|
||||
'@kit/tailwind-config':
|
||||
'@kit/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/tailwind
|
||||
version: link:../../shared
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/typescript
|
||||
@@ -1028,9 +1025,6 @@ importers:
|
||||
'@kit/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/prettier
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/typescript
|
||||
@@ -1052,9 +1046,6 @@ importers:
|
||||
'@kit/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/prettier
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/typescript
|
||||
@@ -1073,9 +1064,6 @@ importers:
|
||||
'@kit/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/prettier
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/typescript
|
||||
@@ -1100,9 +1088,9 @@ importers:
|
||||
'@kit/sentry':
|
||||
specifier: workspace:*
|
||||
version: link:../sentry
|
||||
'@kit/tailwind-config':
|
||||
'@kit/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/tailwind
|
||||
version: link:../../shared
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/typescript
|
||||
@@ -1112,6 +1100,9 @@ importers:
|
||||
react:
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0
|
||||
zod:
|
||||
specifier: ^3.24.1
|
||||
version: 3.24.1
|
||||
|
||||
packages/monitoring/baselime:
|
||||
dependencies:
|
||||
@@ -1131,9 +1122,6 @@ importers:
|
||||
'@kit/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/prettier
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/typescript
|
||||
@@ -1155,9 +1143,6 @@ importers:
|
||||
'@kit/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/prettier
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/typescript
|
||||
@@ -1183,9 +1168,6 @@ importers:
|
||||
'@kit/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/prettier
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tooling/typescript
|
||||
@@ -1213,9 +1195,6 @@ importers:
|
||||
'@kit/supabase':
|
||||
specifier: workspace:*
|
||||
version: link:../supabase
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/typescript
|
||||
@@ -1241,9 +1220,6 @@ importers:
|
||||
'@kit/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/prettier
|
||||
'@kit/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/tailwind
|
||||
'@kit/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/typescript
|
||||
@@ -4122,9 +4098,6 @@ packages:
|
||||
'@types/eslint@8.56.12':
|
||||
resolution: {integrity: sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==}
|
||||
|
||||
'@types/eslint@9.6.1':
|
||||
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
|
||||
|
||||
'@types/estree-jsx@1.0.5':
|
||||
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
||||
|
||||
@@ -11437,7 +11410,7 @@ snapshots:
|
||||
|
||||
'@types/eslint-scope@3.7.7':
|
||||
dependencies:
|
||||
'@types/eslint': 9.6.1
|
||||
'@types/eslint': 8.56.12
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
'@types/eslint@8.56.12':
|
||||
@@ -11445,11 +11418,6 @@ snapshots:
|
||||
'@types/estree': 1.0.6
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@types/eslint@9.6.1':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@types/estree-jsx@1.0.5':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
Reference in New Issue
Block a user