B2B-88: add starter kit structure and elements

This commit is contained in:
devmc-ee
2025-06-08 16:18:30 +03:00
parent 657a36a298
commit e7b25600cb
1280 changed files with 77893 additions and 5688 deletions

View File

@@ -0,0 +1,5 @@
# Analytics - @kit/analytics
@kit/analytics Package provides a simple and consistent API for tracking analytics events in web applications.
Please refer to the [documentation](https://makerkit.dev/docs/next-supabase-turbo/analytics/analytics-and-events).

View File

@@ -0,0 +1,3 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

1
packages/analytics/node_modules/@kit/eslint-config generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../tooling/eslint

1
packages/analytics/node_modules/@kit/prettier-config generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../tooling/prettier

1
packages/analytics/node_modules/@kit/tsconfig generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../tooling/typescript

1
packages/analytics/node_modules/@types/node generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../node_modules/.pnpm/@types+node@22.15.30/node_modules/@types/node

View File

@@ -0,0 +1,28 @@
{
"name": "@kit/analytics",
"private": true,
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/node": "^22.15.18"
},
"typesVersions": {
"*": {
"*": [
"src/*"
]
}
}
}

View File

@@ -0,0 +1,94 @@
import { NullAnalyticsService } from './null-analytics-service';
import type {
AnalyticsManager,
AnalyticsService,
CreateAnalyticsManagerOptions,
} from './types';
export function createAnalyticsManager<T extends string, Config extends object>(
options: CreateAnalyticsManagerOptions<T, Config>,
): AnalyticsManager {
const activeServices = new Map<T, AnalyticsService>();
const getActiveServices = (): AnalyticsService[] => {
if (activeServices.size === 0) {
console.debug(
'No active analytics services. Using NullAnalyticsService.',
);
return [NullAnalyticsService];
}
return Array.from(activeServices.values());
};
const registerActiveServices = (
options: CreateAnalyticsManagerOptions<T, Config>,
) => {
Object.keys(options.providers).forEach((provider) => {
const providerKey = provider as keyof typeof options.providers;
const factory = options.providers[providerKey];
if (!factory) {
console.warn(
`Analytics provider '${provider}' not registered. Skipping initialization.`,
);
return;
}
const service = factory();
activeServices.set(provider as T, service);
void service.initialize();
});
};
registerActiveServices(options);
return {
addProvider: (provider: T, config: Config) => {
const factory = options.providers[provider];
if (!factory) {
console.warn(
`Analytics provider '${provider}' not registered. Skipping initialization.`,
);
return Promise.resolve();
}
const service = factory(config);
activeServices.set(provider, service);
return service.initialize();
},
removeProvider: (provider: T) => {
activeServices.delete(provider);
},
identify: (userId: string, traits?: Record<string, string>) => {
return Promise.all(
getActiveServices().map((service) => service.identify(userId, traits)),
);
},
trackPageView: (path: string) => {
return Promise.all(
getActiveServices().map((service) => service.trackPageView(path)),
);
},
trackEvent: (
eventName: string,
eventProperties?: Record<string, string | string[]>,
) => {
return Promise.all(
getActiveServices().map((service) =>
service.trackEvent(eventName, eventProperties),
),
);
},
};
}

View File

@@ -0,0 +1,9 @@
import { createAnalyticsManager } from './analytics-manager';
import { NullAnalyticsService } from './null-analytics-service';
import type { AnalyticsManager } from './types';
export const analytics: AnalyticsManager = createAnalyticsManager({
providers: {
null: () => NullAnalyticsService,
},
});

View File

@@ -0,0 +1,23 @@
import { AnalyticsService } from './types';
const noop = (event: string) => {
// do nothing - this is to prevent errors when the analytics service is not initialized
return async (...args: unknown[]) => {
console.debug(
`Noop analytics service called with event: ${event}`,
...args.filter(Boolean),
);
};
};
/**
* Null analytics service that does nothing. It is initialized with a noop function. This is useful for testing or when
* the user is calling analytics methods before the analytics service is initialized.
*/
export const NullAnalyticsService: AnalyticsService = {
initialize: noop('initialize'),
trackPageView: noop('trackPageView'),
trackEvent: noop('trackEvent'),
identify: noop('identify'),
};

View File

@@ -0,0 +1,41 @@
interface TrackEvent {
trackEvent(
eventName: string,
eventProperties?: Record<string, string | string[]>,
): Promise<unknown>;
}
interface TrackPageView {
trackPageView(path: string): Promise<unknown>;
}
interface Identify {
identify(userId: string, traits?: Record<string, string>): Promise<unknown>;
}
interface ProviderManager {
addProvider(provider: string, config: object): Promise<unknown>;
removeProvider(provider: string): void;
}
export interface AnalyticsService extends TrackPageView, TrackEvent, Identify {
initialize(): Promise<unknown>;
}
export type AnalyticsProviderFactory<Config extends object> = (
config?: Config,
) => AnalyticsService;
export interface CreateAnalyticsManagerOptions<
T extends string,
Config extends object,
> {
providers: Record<T, AnalyticsProviderFactory<Config>>;
}
export interface AnalyticsManager
extends TrackPageView,
TrackEvent,
Identify,
ProviderManager {}

View File

@@ -0,0 +1,8 @@
{
"extends": "@kit/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}