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

@@ -15,7 +15,6 @@
"devDependencies": { "devDependencies": {
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@types/node": "^22.13.0" "@types/node": "^22.13.0"
}, },

View File

@@ -3,37 +3,44 @@ import 'server-only';
import { z } from 'zod'; import { z } from 'zod';
import { import {
BillingConfig, type BillingConfig,
type BillingProviderSchema, type BillingProviderSchema,
BillingWebhookHandlerService, BillingWebhookHandlerService,
} from '@kit/billing'; } from '@kit/billing';
import { createRegistry } from '@kit/shared/registry';
export class BillingEventHandlerFactoryService { /**
static async GetProviderStrategy( * @description Creates a registry for billing webhook handlers
provider: z.infer<typeof BillingProviderSchema>, * @param config - The billing config
config: BillingConfig, * @returns The billing webhook handler registry
): Promise<BillingWebhookHandlerService> { */
switch (provider) { export function createBillingEventHandlerFactoryService(config: BillingConfig) {
case 'stripe': { // Create a registry for billing webhook handlers
const { StripeWebhookHandlerService } = await import('@kit/stripe'); 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': { return new StripeWebhookHandlerService(config);
const { LemonSqueezyWebhookHandlerService } = await import( });
'@kit/lemon-squeezy'
);
return new LemonSqueezyWebhookHandlerService(config); // Register the Lemon Squeezy webhook handler
} billingWebhookHandlerRegistry.register('lemon-squeezy', async () => {
const { LemonSqueezyWebhookHandlerService } = await import(
'@kit/lemon-squeezy'
);
case 'paddle': { return new LemonSqueezyWebhookHandlerService(config);
throw new Error('Paddle is not supported yet'); });
}
default: // Register Paddle webhook handler (not implemented yet)
throw new Error(`Unsupported billing provider: ${provider as string}`); billingWebhookHandlerRegistry.register('paddle', () => {
} throw new Error('Paddle is not supported yet');
} });
return billingWebhookHandlerRegistry;
} }

View File

@@ -5,7 +5,7 @@ import { SupabaseClient } from '@supabase/supabase-js';
import { BillingConfig } from '@kit/billing'; import { BillingConfig } from '@kit/billing';
import { Database, Enums } from '@kit/supabase/database'; 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'; import { createBillingEventHandlerService } from './billing-event-handler.service';
// a function that returns a Supabase client // a function that returns a Supabase client
@@ -25,10 +25,8 @@ export async function getBillingEventHandlerService(
provider: BillingProvider, provider: BillingProvider,
config: BillingConfig, config: BillingConfig,
) { ) {
const strategy = await BillingEventHandlerFactoryService.GetProviderStrategy( const strategy =
provider, await createBillingEventHandlerFactoryService(config).get(provider);
config,
);
return createBillingEventHandlerService(clientProvider, strategy); return createBillingEventHandlerService(clientProvider, strategy);
} }

View File

@@ -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}`);
}
}
}

View File

@@ -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');
});

View File

@@ -11,7 +11,7 @@ import {
UpdateSubscriptionParamsSchema, UpdateSubscriptionParamsSchema,
} from '@kit/billing/schema'; } from '@kit/billing/schema';
import { BillingGatewayFactoryService } from './billing-gateway-factory.service'; import { billingStrategyRegistry } from './billing-gateway-registry';
export function createBillingGatewayService( export function createBillingGatewayService(
provider: z.infer<typeof BillingProviderSchema>, provider: z.infer<typeof BillingProviderSchema>,
@@ -138,6 +138,6 @@ class BillingGatewayService {
} }
private getStrategy() { private getStrategy() {
return BillingGatewayFactoryService.GetProviderStrategy(this.provider); return billingStrategyRegistry.get(this.provider);
} }
} }

View File

@@ -17,6 +17,7 @@
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/keystatic": "workspace:*", "@kit/keystatic": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@kit/wordpress": "workspace:*", "@kit/wordpress": "workspace:*",
"@types/node": "^22.13.0" "@types/node": "^22.13.0"
@@ -35,4 +36,4 @@
] ]
} }
} }
} }

View File

@@ -1,10 +1,26 @@
import { CmsClient, CmsType } from '@kit/cms-types'; import { CmsClient, CmsType } from '@kit/cms-types';
import { createRegistry } from '@kit/shared/registry';
/** /**
* The type of CMS client to use. * The type of CMS client to use.
*/ */
const CMS_CLIENT = process.env.CMS_CLIENT as CmsType; 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. * 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. * @returns {Promise<CmsClient>} A Promise that resolves to the created CMS client.
* @throws {Error} If the specified CMS type is unknown. * @throws {Error} If the specified CMS type is unknown.
*/ */
export async function createCmsClient( export async function createCmsClient(type: CmsType = CMS_CLIENT) {
type: CmsType = CMS_CLIENT, return cmsRegistry.get(type);
): 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`);
}
} }

View File

@@ -20,7 +20,6 @@
"@kit/shared": "workspace:*", "@kit/shared": "workspace:*",
"@kit/stripe": "workspace:*", "@kit/stripe": "workspace:*",
"@kit/supabase": "workspace:*", "@kit/supabase": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/team-accounts": "workspace:*", "@kit/team-accounts": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@supabase/supabase-js": "2.48.1", "@supabase/supabase-js": "2.48.1",

View File

@@ -19,7 +19,6 @@
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*", "@kit/shared": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@tanstack/react-query": "5.66.0", "@tanstack/react-query": "5.66.0",
"next": "15.1.6", "next": "15.1.6",

View File

@@ -14,10 +14,11 @@
}, },
"devDependencies": { "devDependencies": {
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/mailers-shared": "workspace:*",
"@kit/nodemailer": "workspace:*", "@kit/nodemailer": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/resend": "workspace:*", "@kit/resend": "workspace:*",
"@kit/tailwind-config": "workspace:*", "@kit/shared": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@types/node": "^22.13.0", "@types/node": "^22.13.0",
"zod": "^3.24.1" "zod": "^3.24.1"

View File

@@ -1,37 +1,12 @@
import { z } from 'zod'; import { MAILER_PROVIDER } from './provider-enum';
import { mailerRegistry } from './registry';
const MAILER_PROVIDER = z
.enum(['nodemailer', 'resend'])
.default('nodemailer')
.parse(process.env.MAILER_PROVIDER);
/** /**
* @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() { export function getMailer() {
switch (MAILER_PROVIDER) { return mailerRegistry.get(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}`);
}
} }
async function getNodemailer() { export { MAILER_PROVIDER };
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.',
);
}
}

View 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];

View 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 };

View File

@@ -19,7 +19,6 @@
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/mailers-shared": "workspace:*", "@kit/mailers-shared": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@types/nodemailer": "6.4.17", "@types/nodemailer": "6.4.17",
"zod": "^3.24.1" "zod": "^3.24.1"

View File

@@ -16,7 +16,6 @@
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/mailers-shared": "workspace:*", "@kit/mailers-shared": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@types/node": "^22.13.0", "@types/node": "^22.13.0",
"zod": "^3.24.1" "zod": "^3.24.1"

View File

@@ -15,7 +15,6 @@
"devDependencies": { "devDependencies": {
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"zod": "^3.24.1" "zod": "^3.24.1"
}, },

View File

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

View File

@@ -2,38 +2,64 @@
import { lazy } from 'react'; import { lazy } from 'react';
import { getMonitoringProvider } from '../get-monitoring-provider'; import { createRegistry } from '@kit/shared/registry';
import { InstrumentationProvider } from '../monitoring-providers.enum';
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'); const { BaselimeProvider } = await import('@kit/baselime/provider');
return { 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'); const { SentryProvider } = await import('@kit/sentry/provider');
return { 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) { export function MonitoringProvider(props: React.PropsWithChildren) {
const provider = getMonitoringProvider(); const provider = getMonitoringProvider();
switch (provider) { if (!provider) {
case InstrumentationProvider.Baselime: return <>{props.children}</>;
return (
<BaselimeProvider enableWebVitals>{props.children}</BaselimeProvider>
);
case InstrumentationProvider.Sentry:
return <SentryProvider>{props.children}</SentryProvider>;
default:
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() { export function getMonitoringProvider() {
return process.env.NEXT_PUBLIC_MONITORING_PROVIDER as return MONITORING_PROVIDER.parse(process.env.NEXT_PUBLIC_MONITORING_PROVIDER);
| InstrumentationProvider
| undefined;
} }

View File

@@ -1,37 +1,51 @@
import { getMonitoringProvider } from './get-monitoring-provider'; import { createRegistry } from '@kit/shared/registry';
import { InstrumentationProvider } from './monitoring-providers.enum';
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 * @name registerMonitoringInstrumentation
* @description Register monitoring instrumentation based on the MONITORING_PROVIDER environment variable. * @description Register monitoring instrumentation based on the MONITORING_PROVIDER environment variable using the registry internally.
*
* Please set the MONITORING_PROVIDER environment variable to register the monitoring instrumentation provider.
*/ */
export async function registerMonitoringInstrumentation() { export async function registerMonitoringInstrumentation() {
if (!PROVIDER) { const provider = getMonitoringProvider();
if (!provider) {
return; return;
} }
switch (PROVIDER) { const instrumentation = await instrumentationRegistry.get(provider);
case InstrumentationProvider.Baselime: {
const { registerInstrumentation } = await import(
'@kit/baselime/instrumentation'
);
return registerInstrumentation(); return instrumentation.register();
}
case InstrumentationProvider.Sentry: {
// Sentry v8 automatically sets this up
return;
}
default:
throw new Error(
`Unknown instrumentation provider: ${process.env.NEXT_PUBLIC_MONITORING_PROVIDER}`,
);
}
} }

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 {
import { InstrumentationProvider } from '../monitoring-providers.enum'; 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 * @name getServerMonitoringService
* @description Get the monitoring service based on the MONITORING_PROVIDER environment variable. * @description Get the monitoring service based on the MONITORING_PROVIDER environment variable.
*/ */
export async function getServerMonitoringService() { export async function getServerMonitoringService() {
if (!MONITORING_PROVIDER) { const provider = getMonitoringProvider();
if (!provider) {
console.info( console.info(
`No instrumentation provider specified. Returning console service...`, `No instrumentation provider specified. Returning console service...`,
); );
return new ConsoleMonitoringService(); return new ConsoleMonitoringService();
} }
switch (MONITORING_PROVIDER) { return serverMonitoringRegistry.get(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.`,
);
}
}
} }

View File

@@ -23,7 +23,6 @@
"devDependencies": { "devDependencies": {
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@types/react": "19.0.8", "@types/react": "19.0.8",
"react": "19.0.0", "react": "19.0.0",

View File

@@ -16,7 +16,6 @@
"devDependencies": { "devDependencies": {
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@types/react": "19.0.8", "@types/react": "19.0.8",
"react": "19.0.0" "react": "19.0.0"

View File

@@ -22,7 +22,6 @@
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/monitoring-core": "workspace:*", "@kit/monitoring-core": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@types/react": "19.0.8", "@types/react": "19.0.8",
"react": "19.0.0" "react": "19.0.0"

View File

@@ -19,7 +19,6 @@
"@kit/monitoring": "workspace:*", "@kit/monitoring": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/supabase": "workspace:*", "@kit/supabase": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@supabase/supabase-js": "2.48.1", "@supabase/supabase-js": "2.48.1",
"next": "15.1.6", "next": "15.1.6",

View File

@@ -13,12 +13,12 @@
"./logger": "./src/logger/index.ts", "./logger": "./src/logger/index.ts",
"./utils": "./src/utils.ts", "./utils": "./src/utils.ts",
"./hooks": "./src/hooks/index.ts", "./hooks": "./src/hooks/index.ts",
"./events": "./src/events/index.tsx" "./events": "./src/events/index.tsx",
"./registry": "./src/registry/index.ts"
}, },
"devDependencies": { "devDependencies": {
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*", "@kit/tsconfig": "workspace:*",
"@types/react": "19.0.8" "@types/react": "19.0.8"
}, },

View File

@@ -1,23 +1,25 @@
import { createRegistry } from '../registry';
import { Logger as LoggerInstance } from './logger'; 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';
/* const LOGGER = (process.env.LOGGER ?? 'pino') as LoggerProvider;
* Logger
* By default, the logger is set to use Pino. To change the logger, update the import statement below. // Create a registry for logger implementations
* to your desired logger implementation. 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> { export async function getLogger() {
switch (LOGGER) { return loggerRegistry.get(LOGGER);
case 'pino': {
const { Logger: PinoLogger } = await import('./impl/pino');
return PinoLogger;
}
default:
throw new Error(`Unknown logger: ${process.env.LOGGER}`);
}
} }
export { getLogger };

View 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
View File

@@ -237,9 +237,6 @@ importers:
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/prettier version: link:../../tooling/prettier
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/typescript version: link:../../tooling/typescript
@@ -443,6 +440,9 @@ importers:
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/prettier version: link:../../../tooling/prettier
'@kit/shared':
specifier: workspace:*
version: link:../../shared
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
@@ -555,9 +555,6 @@ importers:
'@kit/supabase': '@kit/supabase':
specifier: workspace:* specifier: workspace:*
version: link:../supabase version: link:../supabase
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../tooling/tailwind
'@kit/team-accounts': '@kit/team-accounts':
specifier: workspace:* specifier: workspace:*
version: link:../features/team-accounts version: link:../features/team-accounts
@@ -964,9 +961,6 @@ importers:
'@kit/shared': '@kit/shared':
specifier: workspace:* specifier: workspace:*
version: link:../shared version: link:../shared
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/typescript version: link:../../tooling/typescript
@@ -991,6 +985,9 @@ importers:
'@kit/eslint-config': '@kit/eslint-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/eslint version: link:../../../tooling/eslint
'@kit/mailers-shared':
specifier: workspace:*
version: link:../shared
'@kit/nodemailer': '@kit/nodemailer':
specifier: workspace:* specifier: workspace:*
version: link:../nodemailer version: link:../nodemailer
@@ -1000,9 +997,9 @@ importers:
'@kit/resend': '@kit/resend':
specifier: workspace:* specifier: workspace:*
version: link:../resend version: link:../resend
'@kit/tailwind-config': '@kit/shared':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/tailwind version: link:../../shared
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
@@ -1028,9 +1025,6 @@ importers:
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/prettier version: link:../../../tooling/prettier
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
@@ -1052,9 +1046,6 @@ importers:
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/prettier version: link:../../../tooling/prettier
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
@@ -1073,9 +1064,6 @@ importers:
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/prettier version: link:../../../tooling/prettier
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
@@ -1100,9 +1088,9 @@ importers:
'@kit/sentry': '@kit/sentry':
specifier: workspace:* specifier: workspace:*
version: link:../sentry version: link:../sentry
'@kit/tailwind-config': '@kit/shared':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/tailwind version: link:../../shared
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
@@ -1112,6 +1100,9 @@ importers:
react: react:
specifier: 19.0.0 specifier: 19.0.0
version: 19.0.0 version: 19.0.0
zod:
specifier: ^3.24.1
version: 3.24.1
packages/monitoring/baselime: packages/monitoring/baselime:
dependencies: dependencies:
@@ -1131,9 +1122,6 @@ importers:
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/prettier version: link:../../../tooling/prettier
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
@@ -1155,9 +1143,6 @@ importers:
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/prettier version: link:../../../tooling/prettier
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
@@ -1183,9 +1168,6 @@ importers:
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/prettier version: link:../../../tooling/prettier
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
@@ -1213,9 +1195,6 @@ importers:
'@kit/supabase': '@kit/supabase':
specifier: workspace:* specifier: workspace:*
version: link:../supabase version: link:../supabase
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/typescript version: link:../../tooling/typescript
@@ -1241,9 +1220,6 @@ importers:
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/prettier version: link:../../tooling/prettier
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/typescript version: link:../../tooling/typescript
@@ -4122,9 +4098,6 @@ packages:
'@types/eslint@8.56.12': '@types/eslint@8.56.12':
resolution: {integrity: sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==} 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': '@types/estree-jsx@1.0.5':
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
@@ -11437,7 +11410,7 @@ snapshots:
'@types/eslint-scope@3.7.7': '@types/eslint-scope@3.7.7':
dependencies: dependencies:
'@types/eslint': 9.6.1 '@types/eslint': 8.56.12
'@types/estree': 1.0.6 '@types/estree': 1.0.6
'@types/eslint@8.56.12': '@types/eslint@8.56.12':
@@ -11445,11 +11418,6 @@ snapshots:
'@types/estree': 1.0.6 '@types/estree': 1.0.6
'@types/json-schema': 7.0.15 '@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': '@types/estree-jsx@1.0.5':
dependencies: dependencies:
'@types/estree': 1.0.6 '@types/estree': 1.0.6