chore(types): ♻️ Refactored TypeScript types in common.ts, events.ts, gdpr.ts, queries.ts, and responses.ts; restructured exports in index.ts to improve organization and reusability
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
a0d6b6fb11
commit
8168ea2f3a
6 changed files with 0 additions and 821 deletions
|
|
@ -1,123 +0,0 @@
|
|||
/**
|
||||
* Time range for analytics queries
|
||||
*/
|
||||
export interface TimeRange {
|
||||
start: Date;
|
||||
end: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Granularity for time-series data
|
||||
*/
|
||||
export type TimeGranularity = 'minute' | 'hour' | 'day' | 'week' | 'month';
|
||||
|
||||
/**
|
||||
* Device information collected from client
|
||||
*/
|
||||
export interface DeviceData {
|
||||
userAgent: string;
|
||||
platform: string;
|
||||
language: string;
|
||||
screenWidth: number;
|
||||
screenHeight: number;
|
||||
viewportWidth: number;
|
||||
viewportHeight: number;
|
||||
devicePixelRatio: number;
|
||||
timezone: string;
|
||||
touchSupported: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribution data for tracking source
|
||||
*/
|
||||
export interface AttributionData {
|
||||
source?: string;
|
||||
medium?: string;
|
||||
campaign?: string;
|
||||
term?: string;
|
||||
content?: string;
|
||||
referrer?: string;
|
||||
landingPage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Session fingerprint (privacy-preserving)
|
||||
*/
|
||||
export interface SessionFingerprint {
|
||||
sessionId: string;
|
||||
visitorId: string; // Hashed, not persistent
|
||||
firstSeen: Date;
|
||||
lastSeen: Date;
|
||||
pageViews: number;
|
||||
attribution?: AttributionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Geolocation data (privacy-preserving)
|
||||
*/
|
||||
export interface GeoLocation {
|
||||
country?: string;
|
||||
region?: string;
|
||||
city?: string;
|
||||
// Note: No coordinates or precise location
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination parameters
|
||||
*/
|
||||
export interface PaginationParams {
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginated response wrapper
|
||||
*/
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metric value with timestamp
|
||||
*/
|
||||
export interface TimestampedMetric {
|
||||
timestamp: Date;
|
||||
value: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metric with comparison to previous period
|
||||
*/
|
||||
export interface MetricWithComparison {
|
||||
current: number;
|
||||
previous: number;
|
||||
changePercent: number;
|
||||
trend: 'up' | 'down' | 'stable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event metadata (automatic context collection)
|
||||
*/
|
||||
export interface EventMetadata {
|
||||
userAgent?: string;
|
||||
language?: string;
|
||||
screenWidth?: number;
|
||||
screenHeight?: number;
|
||||
viewportWidth?: number;
|
||||
viewportHeight?: number;
|
||||
timezone?: string;
|
||||
timestamp?: string;
|
||||
referrer?: string;
|
||||
nodeVersion?: string;
|
||||
platform?: string;
|
||||
utm_source?: string;
|
||||
utm_medium?: string;
|
||||
utm_campaign?: string;
|
||||
utm_term?: string;
|
||||
utm_content?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
import type { AttributionData, DeviceData } from './common';
|
||||
|
||||
/**
|
||||
* Base event interface - all events extend this
|
||||
*/
|
||||
export interface BaseEvent {
|
||||
/** Event type identifier */
|
||||
type: string;
|
||||
/** ISO timestamp of when event occurred */
|
||||
timestamp: string;
|
||||
/** Session identifier */
|
||||
sessionId: string;
|
||||
/** Hashed visitor identifier (not persistent across sessions) */
|
||||
visitorId?: string;
|
||||
/** User ID if authenticated */
|
||||
userId?: string;
|
||||
/** Arbitrary metadata */
|
||||
properties?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page view event
|
||||
*/
|
||||
export interface PageViewEvent extends BaseEvent {
|
||||
type: 'page_view';
|
||||
properties: {
|
||||
url: string;
|
||||
title?: string;
|
||||
referrer?: string;
|
||||
path: string;
|
||||
queryParams?: Record<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Click event
|
||||
*/
|
||||
export interface ClickEvent extends BaseEvent {
|
||||
type: 'click';
|
||||
properties: {
|
||||
elementId?: string;
|
||||
elementClass?: string;
|
||||
elementTag: string;
|
||||
elementText?: string;
|
||||
href?: string;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission event
|
||||
*/
|
||||
export interface FormSubmitEvent extends BaseEvent {
|
||||
type: 'form_submit';
|
||||
properties: {
|
||||
formId?: string;
|
||||
formName?: string;
|
||||
formAction?: string;
|
||||
fieldCount: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom event (user-defined)
|
||||
*/
|
||||
export interface CustomEvent extends BaseEvent {
|
||||
type: 'custom';
|
||||
properties: {
|
||||
name: string;
|
||||
category?: string;
|
||||
label?: string;
|
||||
value?: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversion event (goal completion)
|
||||
*/
|
||||
export interface ConversionEvent extends BaseEvent {
|
||||
type: 'conversion';
|
||||
properties: {
|
||||
goalId: string;
|
||||
goalName: string;
|
||||
value?: number;
|
||||
currency?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Funnel step event
|
||||
*/
|
||||
export interface FunnelStepEvent extends BaseEvent {
|
||||
type: 'funnel_step';
|
||||
properties: {
|
||||
funnelId: string;
|
||||
stepId: string;
|
||||
stepNumber: number;
|
||||
stepName: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Revenue event (transaction)
|
||||
*/
|
||||
export interface RevenueEvent extends BaseEvent {
|
||||
type: 'revenue';
|
||||
properties: {
|
||||
transactionId: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
productIds?: string[];
|
||||
category?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Error event
|
||||
*/
|
||||
export interface ErrorEvent extends BaseEvent {
|
||||
type: 'error';
|
||||
properties: {
|
||||
message: string;
|
||||
stack?: string;
|
||||
source?: string;
|
||||
line?: number;
|
||||
column?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Session start event
|
||||
*/
|
||||
export interface SessionStartEvent extends BaseEvent {
|
||||
type: 'session_start';
|
||||
properties: {
|
||||
isNewVisitor: boolean;
|
||||
landingPage: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Session end event
|
||||
*/
|
||||
export interface SessionEndEvent extends BaseEvent {
|
||||
type: 'session_end';
|
||||
properties: {
|
||||
duration: number;
|
||||
pageViews: number;
|
||||
events: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type of all event types
|
||||
*/
|
||||
export type AnalyticsEvent =
|
||||
| PageViewEvent
|
||||
| ClickEvent
|
||||
| FormSubmitEvent
|
||||
| CustomEvent
|
||||
| ConversionEvent
|
||||
| FunnelStepEvent
|
||||
| RevenueEvent
|
||||
| ErrorEvent
|
||||
| SessionStartEvent
|
||||
| SessionEndEvent;
|
||||
|
||||
/**
|
||||
* Event payload sent to collector
|
||||
*/
|
||||
export interface TrackEventPayload {
|
||||
events: AnalyticsEvent[];
|
||||
device?: DeviceData;
|
||||
attribution?: AttributionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event type string literals
|
||||
*/
|
||||
export type EventType = AnalyticsEvent['type'];
|
||||
|
||||
/**
|
||||
* Event properties by type
|
||||
*/
|
||||
export type EventProperties<T extends EventType> = Extract<
|
||||
AnalyticsEvent,
|
||||
{ type: T }
|
||||
>['properties'];
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
/**
|
||||
* GDPR data subject request types
|
||||
*/
|
||||
export type GdprRequestType = 'export' | 'delete' | 'rectify';
|
||||
|
||||
/**
|
||||
* GDPR request status
|
||||
*/
|
||||
export type GdprRequestStatus =
|
||||
| 'pending'
|
||||
| 'processing'
|
||||
| 'completed'
|
||||
| 'failed'
|
||||
| 'cancelled';
|
||||
|
||||
/**
|
||||
* GDPR data subject request
|
||||
*/
|
||||
export interface GdprRequest {
|
||||
id: string;
|
||||
type: GdprRequestType;
|
||||
subjectId: string;
|
||||
subjectEmail?: string;
|
||||
status: GdprRequestStatus;
|
||||
createdAt: Date;
|
||||
processedAt?: Date;
|
||||
completedAt?: Date;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* GDPR export data structure
|
||||
*/
|
||||
export interface GdprExportData {
|
||||
subjectId: string;
|
||||
exportedAt: Date;
|
||||
data: {
|
||||
sessions: GdprSessionExport[];
|
||||
events: GdprEventExport[];
|
||||
attributionData?: GdprAttributionExport;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Session export for GDPR
|
||||
*/
|
||||
export interface GdprSessionExport {
|
||||
sessionId: string;
|
||||
startedAt: Date;
|
||||
endedAt?: Date;
|
||||
pageViews: number;
|
||||
country?: string;
|
||||
deviceType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event export for GDPR
|
||||
*/
|
||||
export interface GdprEventExport {
|
||||
timestamp: Date;
|
||||
type: string;
|
||||
page?: string;
|
||||
properties: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribution export for GDPR
|
||||
*/
|
||||
export interface GdprAttributionExport {
|
||||
firstTouch?: {
|
||||
source?: string;
|
||||
medium?: string;
|
||||
campaign?: string;
|
||||
timestamp: Date;
|
||||
};
|
||||
lastTouch?: {
|
||||
source?: string;
|
||||
medium?: string;
|
||||
campaign?: string;
|
||||
timestamp: Date;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* GDPR deletion audit log
|
||||
*/
|
||||
export interface GdprDeletionAudit {
|
||||
id: string;
|
||||
subjectId: string;
|
||||
deletedAt: Date;
|
||||
tablesAffected: string[];
|
||||
rowsDeleted: number;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data retention policy
|
||||
*/
|
||||
export interface DataRetentionPolicy {
|
||||
name: string;
|
||||
tableName: string;
|
||||
retentionDays: number;
|
||||
enabled: boolean;
|
||||
lastRunAt?: Date;
|
||||
rowsDeleted?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consent record
|
||||
*/
|
||||
export interface ConsentRecord {
|
||||
subjectId: string;
|
||||
consentedAt: Date;
|
||||
withdrawnAt?: Date;
|
||||
purposes: ConsentPurpose[];
|
||||
source: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consent purpose
|
||||
*/
|
||||
export type ConsentPurpose =
|
||||
| 'analytics'
|
||||
| 'marketing'
|
||||
| 'personalization'
|
||||
| 'functional';
|
||||
|
||||
/**
|
||||
* DoNotTrack status
|
||||
*/
|
||||
export interface DoNotTrackStatus {
|
||||
enabled: boolean;
|
||||
source: 'browser' | 'gpc' | 'cookie' | 'explicit';
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
// Event Types
|
||||
export * from './events';
|
||||
|
||||
// Query Types
|
||||
export * from './queries';
|
||||
|
||||
// Response Types
|
||||
export * from './responses';
|
||||
|
||||
// Common Types
|
||||
export * from './common';
|
||||
|
||||
// GDPR Types
|
||||
export * from './gdpr';
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
import type { EventType } from './events';
|
||||
import type { TimeGranularity, TimeRange, PaginationParams } from './common';
|
||||
|
||||
/**
|
||||
* Base query parameters
|
||||
*/
|
||||
export interface BaseQueryParams {
|
||||
timeRange: TimeRange;
|
||||
filters?: QueryFilter[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter operator
|
||||
*/
|
||||
export type FilterOperator =
|
||||
| 'equals'
|
||||
| 'not_equals'
|
||||
| 'contains'
|
||||
| 'not_contains'
|
||||
| 'starts_with'
|
||||
| 'ends_with'
|
||||
| 'greater_than'
|
||||
| 'less_than'
|
||||
| 'in'
|
||||
| 'not_in';
|
||||
|
||||
/**
|
||||
* Query filter
|
||||
*/
|
||||
export interface QueryFilter {
|
||||
field: string;
|
||||
operator: FilterOperator;
|
||||
value: string | number | boolean | string[] | number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Time series query parameters
|
||||
*/
|
||||
export interface TimeSeriesQueryParams extends BaseQueryParams {
|
||||
granularity: TimeGranularity;
|
||||
metric: string;
|
||||
groupBy?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Funnel query parameters
|
||||
*/
|
||||
export interface FunnelQueryParams extends BaseQueryParams {
|
||||
steps: FunnelStep[];
|
||||
conversionWindow?: number; // seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* Funnel step definition
|
||||
*/
|
||||
export interface FunnelStep {
|
||||
name: string;
|
||||
eventType: EventType;
|
||||
filters?: QueryFilter[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cohort query parameters
|
||||
*/
|
||||
export interface CohortQueryParams extends BaseQueryParams {
|
||||
cohortType: CohortType;
|
||||
granularity: TimeGranularity;
|
||||
metric: CohortMetric;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cohort type
|
||||
*/
|
||||
export type CohortType = 'first_visit' | 'signup' | 'first_purchase' | 'custom';
|
||||
|
||||
/**
|
||||
* Cohort metric
|
||||
*/
|
||||
export type CohortMetric =
|
||||
| 'retention'
|
||||
| 'revenue'
|
||||
| 'sessions'
|
||||
| 'conversions';
|
||||
|
||||
/**
|
||||
* Retention query parameters
|
||||
*/
|
||||
export interface RetentionQueryParams extends BaseQueryParams {
|
||||
retentionType: RetentionType;
|
||||
periods: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retention type
|
||||
*/
|
||||
export type RetentionType = 'day' | 'week' | 'month';
|
||||
|
||||
/**
|
||||
* Top pages query parameters
|
||||
*/
|
||||
export interface TopPagesQueryParams extends BaseQueryParams, PaginationParams {
|
||||
metric: 'views' | 'unique_visitors' | 'avg_time_on_page';
|
||||
sortDirection: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Revenue query parameters
|
||||
*/
|
||||
export interface RevenueQueryParams extends BaseQueryParams {
|
||||
granularity: TimeGranularity;
|
||||
groupBy?: 'source' | 'medium' | 'campaign' | 'country';
|
||||
}
|
||||
|
||||
/**
|
||||
* Realtime query parameters
|
||||
*/
|
||||
export interface RealtimeQueryParams {
|
||||
metrics: RealtimeMetric[];
|
||||
interval?: number; // seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* Realtime metric
|
||||
*/
|
||||
export type RealtimeMetric =
|
||||
| 'active_users'
|
||||
| 'page_views_per_minute'
|
||||
| 'events_per_minute'
|
||||
| 'top_pages'
|
||||
| 'top_referrers';
|
||||
|
||||
/**
|
||||
* User activity query parameters
|
||||
*/
|
||||
export interface UserActivityQueryParams extends BaseQueryParams, PaginationParams {
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
|
@ -1,222 +0,0 @@
|
|||
import type { MetricWithComparison, TimestampedMetric, PaginatedResponse } from './common';
|
||||
|
||||
/**
|
||||
* Dashboard overview response
|
||||
*/
|
||||
export interface DashboardOverview {
|
||||
visitors: MetricWithComparison;
|
||||
pageViews: MetricWithComparison;
|
||||
sessions: MetricWithComparison;
|
||||
avgSessionDuration: MetricWithComparison;
|
||||
bounceRate: MetricWithComparison;
|
||||
conversions: MetricWithComparison;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time series data point
|
||||
*/
|
||||
export interface TimeSeriesDataPoint {
|
||||
timestamp: Date;
|
||||
value: number;
|
||||
groupKey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time series response
|
||||
*/
|
||||
export interface TimeSeriesResponse {
|
||||
data: TimeSeriesDataPoint[];
|
||||
total: number;
|
||||
granularity: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Funnel step result
|
||||
*/
|
||||
export interface FunnelStepResult {
|
||||
stepNumber: number;
|
||||
stepName: string;
|
||||
count: number;
|
||||
conversionRate: number;
|
||||
dropoffRate: number;
|
||||
avgTimeToConvert?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Funnel analysis response
|
||||
*/
|
||||
export interface FunnelResponse {
|
||||
steps: FunnelStepResult[];
|
||||
overallConversionRate: number;
|
||||
totalStarted: number;
|
||||
totalCompleted: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cohort bucket
|
||||
*/
|
||||
export interface CohortBucket {
|
||||
cohortDate: Date;
|
||||
cohortSize: number;
|
||||
periods: CohortPeriod[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cohort period data
|
||||
*/
|
||||
export interface CohortPeriod {
|
||||
periodNumber: number;
|
||||
value: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cohort analysis response
|
||||
*/
|
||||
export interface CohortResponse {
|
||||
cohorts: CohortBucket[];
|
||||
averageRetention: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retention data point
|
||||
*/
|
||||
export interface RetentionDataPoint {
|
||||
period: string;
|
||||
retained: number;
|
||||
total: number;
|
||||
rate: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retention response
|
||||
*/
|
||||
export interface RetentionResponse {
|
||||
data: RetentionDataPoint[];
|
||||
d1: number;
|
||||
d7: number;
|
||||
d30: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top page entry
|
||||
*/
|
||||
export interface TopPageEntry {
|
||||
path: string;
|
||||
title?: string;
|
||||
views: number;
|
||||
uniqueVisitors: number;
|
||||
avgTimeOnPage: number;
|
||||
bounceRate: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top pages response
|
||||
*/
|
||||
export type TopPagesResponse = PaginatedResponse<TopPageEntry>;
|
||||
|
||||
/**
|
||||
* Revenue summary
|
||||
*/
|
||||
export interface RevenueSummary {
|
||||
total: MetricWithComparison;
|
||||
transactions: MetricWithComparison;
|
||||
avgOrderValue: MetricWithComparison;
|
||||
revenuePerUser: MetricWithComparison;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revenue by source
|
||||
*/
|
||||
export interface RevenueBySource {
|
||||
source: string;
|
||||
revenue: number;
|
||||
transactions: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revenue response
|
||||
*/
|
||||
export interface RevenueResponse {
|
||||
summary: RevenueSummary;
|
||||
timeSeries: TimestampedMetric[];
|
||||
bySource?: RevenueBySource[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Realtime metrics
|
||||
*/
|
||||
export interface RealtimeMetrics {
|
||||
activeUsers: number;
|
||||
pageViewsPerMinute: number;
|
||||
eventsPerMinute: number;
|
||||
topPages: Array<{ path: string; count: number }>;
|
||||
topReferrers: Array<{ referrer: string; count: number }>;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* User activity entry
|
||||
*/
|
||||
export interface UserActivityEntry {
|
||||
timestamp: Date;
|
||||
eventType: string;
|
||||
properties: Record<string, unknown>;
|
||||
sessionId: string;
|
||||
page?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User activity response
|
||||
*/
|
||||
export type UserActivityResponse = PaginatedResponse<UserActivityEntry>;
|
||||
|
||||
/**
|
||||
* Attribution data
|
||||
*/
|
||||
export interface AttributionReport {
|
||||
source: string;
|
||||
medium?: string;
|
||||
campaign?: string;
|
||||
visitors: number;
|
||||
conversions: number;
|
||||
revenue: number;
|
||||
conversionRate: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribution response
|
||||
*/
|
||||
export interface AttributionResponse {
|
||||
data: AttributionReport[];
|
||||
topSource: string;
|
||||
topCampaign?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* LTV (Lifetime Value) response
|
||||
*/
|
||||
export interface LtvResponse {
|
||||
averageLtv: number;
|
||||
ltvByCohort: Array<{
|
||||
cohort: string;
|
||||
ltv: number;
|
||||
customers: number;
|
||||
}>;
|
||||
projectedLtv: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* CAC (Customer Acquisition Cost) response
|
||||
*/
|
||||
export interface CacResponse {
|
||||
averageCac: number;
|
||||
cacByChannel: Array<{
|
||||
channel: string;
|
||||
cac: number;
|
||||
customers: number;
|
||||
spend: number;
|
||||
}>;
|
||||
ltvCacRatio: number;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue