@shopkit/events
v0.5.0
Published
Event-driven analytics system for OpenStore storefronts
Downloads
174
Maintainers
Readme
@shopkit/events
Event-driven analytics system for e-commerce storefronts.
Features
- 20 standardized events aligned with GA4 enhanced e-commerce
- Type-safe EventBus with middleware pipeline and ring buffer
- 5-stage middleware — schema validation, deduplication, user enrichment, dev logging, server relay
- Cart integration via
onCartEventcallback (zero changes to@shopkit/cart) - Automatic page views with page type detection
- Product view tracking with 20s engagement timer
- Data mappers for cart, product, and order data (with priceDivisor for paise→rupees conversion)
- sendBeacon batching with fetch keepalive fallback
- PII hashing via Web Crypto API (deferred, non-blocking)
- Performance budget of 3.2ms per event end-to-end
Installation
npm install @shopkit/events
# or
bun add @shopkit/eventsPeer dependencies: react, zod, next (optional), zustand (optional)
Quick Start
1. Add EventProvider in your root layout
// app/layout.tsx
'use client';
import { EventProvider } from '@shopkit/events/react';
import { usePathname, useSearchParams } from 'next/navigation';
function EventProviderWrapper({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const searchParams = useSearchParams()?.toString();
return (
<EventProvider pathname={pathname} searchParams={searchParams}>
{children}
</EventProvider>
);
}
export default function RootLayout({ children }) {
return (
<html>
<body>
<EventProviderWrapper>{children}</EventProviderWrapper>
</body>
</html>
);
}2. Wire cart events
// bootstrap/cart.ts
import { eventBus } from '@shopkit/events';
import { createCartMapper } from '@shopkit/events/mappers';
import { createCartEventHandler } from '@shopkit/events/emitters';
import { configureCart } from '@shopkit/cart';
// priceDivisor: 100 converts paise (35500) to rupees (355.00)
const mapper = createCartMapper('INR', 100);
const handleCartEvent = createCartEventHandler(eventBus, mapper);
configureCart({
// ...existing config...
onCartEvent: handleCartEvent,
});3. Track product views
// components/ProductPage.tsx
import { useProductViewEmitter } from '@shopkit/events/emitters';
import { useEventBus } from '@shopkit/events/react';
function ProductPage({ product }) {
const bus = useEventBus();
const standardItem = {
item_id: product.id,
item_name: product.title,
item_brand: product.vendor,
item_category: product.productType,
price: product.price,
quantity: 1,
currency: 'INR',
};
useProductViewEmitter(bus, standardItem);
return <div>{/* product UI */}</div>;
}4. Emit events manually
import { useTrackEvent } from '@shopkit/events/react';
import { OpenStoreEventType } from '@shopkit/events';
function SearchResults({ term, count }) {
const emit = useTrackEvent();
useEffect(() => {
emit(OpenStoreEventType.SEARCH, {
search_term: term,
results_count: count,
});
}, [term, count]);
return <div>{/* results */}</div>;
}API Reference
Core
eventBus
Singleton EventBus instance. The single point through which all analytics events flow.
| Method | Signature | Description |
|--------|-----------|-------------|
| subscribe | (type: OpenStoreEventType, handler) => Unsubscribe | Subscribe to a specific event type |
| subscribeAll | (handler) => Unsubscribe | Subscribe to all events |
| emit | (type, data, overrides?) => OpenStoreEvent \| null | Emit an event (null if blocked by middleware) |
| use | (middleware: Middleware) => void | Register middleware |
| getEventLog | () => ReadonlyArray<OpenStoreEvent> | Get ring buffer of last 100 events |
| reset | () => void | Clear all state (testing only) |
Event Types
import { OpenStoreEventType } from '@shopkit/events';
// 16 client-side events
OpenStoreEventType.PAGE_VIEW
OpenStoreEventType.VIEW_PRODUCT
OpenStoreEventType.ENGAGE_CONTENT
OpenStoreEventType.ADD_TO_CART
OpenStoreEventType.REMOVE_FROM_CART
OpenStoreEventType.VIEW_CART
OpenStoreEventType.BEGIN_CHECKOUT
OpenStoreEventType.ADD_PAYMENT_INFO
OpenStoreEventType.ADD_SHIPPING_INFO
OpenStoreEventType.PURCHASE
OpenStoreEventType.SEARCH
OpenStoreEventType.ADD_TO_WISHLIST
OpenStoreEventType.KWIKPASS_LOGIN_ATTEMPTED
OpenStoreEventType.KWIKPASS_LOGIN_COMPLETED
OpenStoreEventType.VIEW_PROMO
OpenStoreEventType.AB_TEST_VIEWED
// 4 server-side events
OpenStoreEventType.ORDER_FULFILLED
OpenStoreEventType.ORDER_SHIPPED
OpenStoreEventType.ORDER_DELIVERED
OpenStoreEventType.ORDER_CANCELLEDMiddleware (@shopkit/events/middleware)
| Middleware | Factory | Description |
|-----------|---------|-------------|
| schemaValidatorMiddleware | createSchemaValidatorMiddleware() | Validates payloads against Zod schemas |
| deduplicatorMiddleware | createDeduplicatorMiddleware(windowMs?) | Blocks duplicate events within 2s window |
| userEnricherMiddleware | createUserEnricherMiddleware() | Enriches with cookies, affiliate data, PII hashes |
| devLoggerMiddleware | createDevLoggerMiddleware() | Console logging in development |
| serverRelayMiddleware | createServerRelayMiddleware(config?) | Batches and sends via sendBeacon |
Server Relay Config
interface ServerRelayConfig {
ingestUrl?: string; // Default: /api/events/ingest
flushIntervalMs?: number; // Default: 5000
maxBatchSize?: number; // Default: 10
}Emitters (@shopkit/events/emitters)
createCartEventHandler(bus, mapper, options?)
Creates a handler for @shopkit/cart's onCartEvent callback.
interface CartEmitterOptions {
getCartItems?: () => CartItemLike[];
productIdentifier?: ProductIdentifier; // Override bus.productIdentifier
}The handler normalizes raw cart store items (snake_case fields, price as number) automatically. productIdentifier is read lazily from bus.productIdentifier at event-fire time, so it respects config set by EventProvider even if the cart is bootstrapped first.
<PageEmitter bus={bus} pathname={pathname} searchParams={searchParams} />
React component that emits page_view on route changes. Rendered automatically by EventProvider.
useProductViewEmitter(bus, product, options?)
Hook that emits view_product on mount and engage_content after 20s dwell time.
Mappers (@shopkit/events/mappers)
| Mapper | Factory | Description |
|--------|---------|-------------|
| CartMapper | createCartMapper(currency?, priceDivisor?) | CartItem to ProductFields. priceDivisor converts raw price units (e.g. 100 for paise→rupees) |
| ShopifyProductMapper | createShopifyProductMapper(currency?) | Product GraphQL to StandardItem |
| ShopifyOrderMapper | createShopifyOrderMapper(currency?) | Order to PurchasePayload |
Note:
ShopifyCartMapperandcreateShopifyCartMapperare deprecated aliases forCartMapperandcreateCartMapper.
React (@shopkit/events/react)
<EventProvider config? pathname? searchParams?>
Root provider. Initializes EventBus with middleware pipeline and renders PageEmitter.
interface EventProviderConfig {
enableDevTools?: boolean;
ingestUrl?: string;
disablePageView?: boolean;
disableServerRelay?: boolean;
productIdentifier?: ProductIdentifier; // "product_id" | "sku" | "variant_id"
bus?: EventBus; // For testing
}Hooks
| Hook | Return Type | Description |
|------|-------------|-------------|
| useEventBus() | EventBus | Access EventBus from context |
| useTrackEvent() | (type, data) => OpenStoreEvent \| null | Stable emit function |
| useEventSubscribe(type, handler) | void | Subscribe with auto-cleanup |
| useEventSubscribeAll(handler) | void | Subscribe to all events |
| useEventLog() | ReadonlyArray<OpenStoreEvent> | Access event ring buffer |
Schemas (@shopkit/events/schemas)
Zod validation schemas for all 20 events plus shared data shapes.
| Export | Description |
|--------|-------------|
| StandardItemSchema | 15-field GA4-aligned item schema |
| StandardUserDataSchema | PII hashed user data schema |
| StandardCookiesSchema | Platform tracking cookies schema |
| eventPayloadSchemas | Record mapping event type to Zod schema |
| OpenStoreEventEnvelopeSchema | Full event envelope structural schema |
Sub-path Exports
import { eventBus, OpenStoreEventType } from '@shopkit/events';
import { schemaValidatorMiddleware } from '@shopkit/events/middleware';
import { createCartEventHandler } from '@shopkit/events/emitters';
import { createCartMapper } from '@shopkit/events/mappers';
import { EventProvider, useTrackEvent } from '@shopkit/events/react';
import { eventPayloadSchemas } from '@shopkit/events/schemas';License
MIT
