Type-Safe by Default
Grain SDK is written in TypeScript and includes complete type definitions:@types packages needed - types are built-in.
Type-safe analytics with full TypeScript support
import { createGrainAnalytics, GrainAnalytics } from '@grainql/analytics-web';
const grain: GrainAnalytics = createGrainAnalytics({
tenantId: 'your-tenant-id'
});
@types packages needed - types are built-in.
import { GrainConfig } from '@grainql/analytics-web';
const config: GrainConfig = {
tenantId: 'your-tenant-id',
authStrategy: 'JWT', // 'NONE' | 'SERVER_SIDE' | 'JWT'
batchSize: 50,
debug: true
};
const grain = createGrainAnalytics(config);
interface ButtonClickEvent {
button_name: string;
page: string;
timestamp: number;
}
grain.track('button_clicked', {
button_name: 'signup',
page: '/home',
timestamp: Date.now()
} as ButtonClickEvent);
function trackButtonClick(button: string, page: string) {
grain.track('button_clicked', {
button_name: button,
page,
timestamp: Date.now()
});
}
import { LoginEventProperties } from '@grainql/analytics-web';
const loginEvent: LoginEventProperties = {
method: 'email',
success: true,
rememberMe: false
};
await grain.trackLogin(loginEvent);
// TypeScript knows all valid properties
type CustomEventName =
| 'video_started'
| 'video_paused'
| 'video_completed';
interface VideoEventProps {
video_id: string;
duration: number;
progress: number;
}
function trackVideoEvent(
name: CustomEventName,
props: VideoEventProps
) {
grain.track(name, props);
}
// Type-safe usage
trackVideoEvent('video_started', {
video_id: 'abc123',
duration: 120,
progress: 0
});
import { useConfig } from '@grainql/analytics-web/react';
function Component() {
// TypeScript infers return type
const { value, isRefreshing, error, refresh } = useConfig('hero_text');
// value: string | undefined
// isRefreshing: boolean
// error: Error | null
// refresh: () => Promise<void>
}
import { GrainAnalytics } from '@grainql/analytics-web';
class AnalyticsService {
constructor(private grain: GrainAnalytics) {}
trackPageView(page: string) {
this.grain.track('page_viewed', { page });
}
}
const service = new AnalyticsService(grain);
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true
}
}
const userId = grain.getUserId();
// userId: string | null
if (userId) {
console.log(userId.toUpperCase()); // Safe
}
import { AuthProvider } from '@grainql/analytics-web';
const authProvider: AuthProvider = {
async getToken(): Promise<string> {
const token = await auth0.getAccessToken();
return token;
}
};
const grain = createGrainAnalytics({
tenantId: 'your-tenant-id',
authStrategy: 'JWT',
authProvider
});
interface AppConfig {
hero_text: string;
button_color: string;
feature_enabled: 'true' | 'false';
}
// Type-safe config access
function getTypedConfig<K extends keyof AppConfig>(
key: K
): AppConfig[K] | undefined {
return grain.getConfig(key) as AppConfig[K] | undefined;
}
const heroText = getTypedConfig('hero_text'); // string | undefined
const color = getTypedConfig('button_color'); // string | undefined
enum EventName {
PAGE_VIEWED = 'page_viewed',
BUTTON_CLICKED = 'button_clicked',
FORM_SUBMITTED = 'form_submitted'
}
grain.track(EventName.PAGE_VIEWED, { page: '/home' });
grain.track(EventName.BUTTON_CLICKED, { button: 'signup' });
function isConfigLoaded(value: string | undefined): value is string {
return value !== undefined && value.length > 0;
}
const config = grain.getConfig('feature_flag');
if (isConfigLoaded(config)) {
// TypeScript knows config is string here
console.log(config.toUpperCase());
}
// types/analytics.ts
export type {
GrainAnalytics,
GrainConfig,
GrainEvent,
AuthProvider,
LoginEventProperties,
CheckoutEventProperties
} from '@grainql/analytics-web';
export type {
UseConfigResult,
UseAllConfigsResult
} from '@grainql/analytics-web/react';
// components/MyComponent.tsx
import type { UseConfigResult } from '@/types/analytics';
import { GrainAnalytics } from '@grainql/analytics-web';
const mockGrain: jest.Mocked<GrainAnalytics> = {
track: jest.fn(),
setUserId: jest.fn(),
getConfig: jest.fn(),
// ... other methods
} as any;
// Type-safe mock usage
mockGrain.track('test_event', { foo: 'bar' });
expect(mockGrain.track).toHaveBeenCalledWith(
'test_event',
{ foo: 'bar' }
);
class TypedGrainAnalytics {
constructor(private grain: GrainAnalytics) {}
trackSignup(method: 'email' | 'google' | 'github') {
this.grain.track('signup', { method });
}
trackPurchase(amount: number, currency: 'USD' | 'EUR' | 'GBP') {
this.grain.track('purchase', { amount, currency });
}
}
const analytics = new TypedGrainAnalytics(grain);
analytics.trackSignup('email'); // Type-safe
// analytics.trackSignup('facebook'); // Error: invalid method
import type { GrainConfig } from '@grainql/analytics-web';
Was this page helpful?