@@ -13,6 +13,10 @@ import { EventTracker, type EventTrackerService, type BaseTrackingEvent } from "
1313export interface HttpBatchTrackerConfig {
1414 /** URL to send events to */
1515 readonly endpoint : string ;
16+ /** Environment identifier (e.g., "production", "staging") */
17+ readonly env : string ;
18+ /** User-defined service key for identifying the service */
19+ readonly serviceKey : string ;
1620 /** Maximum events per batch (default: 100) */
1721 readonly batchSize ?: number ;
1822 /** Headers to include in requests */
@@ -85,61 +89,82 @@ export function createHttpBatchTracker<E extends BaseTrackingEvent>(
8589 retry : { ...DEFAULT_CONFIG . retry , ...config . retry } ,
8690 } ;
8791
92+ // Default enrichment adds env and serviceKey to events
93+ const defaultEnrich = ( event : E ) : E & { env : string ; serviceKey : string } => ( {
94+ ...event ,
95+ env : config . env ,
96+ serviceKey : config . serviceKey ,
97+ } ) ;
98+
99+ // Compose enrichment: apply default first, then custom if provided
100+ const enrich = enrichFn
101+ ? ( event : E ) => enrichFn ( defaultEnrich ( event ) as E )
102+ : defaultEnrich ;
103+
88104 return Effect . gen ( function * ( ) {
89105 const buffer = yield * Ref . make < E [ ] > ( [ ] ) ;
90106
91107 // Send batch to endpoint
92108 const sendBatch = ( events : E [ ] ) : Effect . Effect < void > => {
93- if ( events . length === 0 ) return Effect . void ;
109+ if ( events . length === 0 ) {
110+ return Effect . void ;
111+ }
94112
95- // Apply enrichment if provided
96- const wireEvents = enrichFn ? events . map ( enrichFn ) : events ;
113+ // Apply enrichment (adds env and serviceKey)
114+ const wireEvents = events . map ( enrich ) ;
97115
98116 return Effect . tryPromise ( {
99117 try : async ( ) => {
118+ const body = JSON . stringify ( { events : wireEvents } ) ;
119+
100120 const response = await fetch ( cfg . endpoint , {
101121 method : "POST" ,
102122 headers : {
103123 "Content-Type" : "application/json" ,
104124 ...cfg . headers ,
105125 } ,
106- body : JSON . stringify ( { events : wireEvents } ) ,
126+ body,
107127 } ) ;
108128
109129 if ( ! response . ok ) {
130+ const errorText = await response . text ( ) . catch ( ( ) => "unable to read response" ) ;
110131 throw new HttpTrackerError (
111132 "send" ,
112- `HTTP ${ response . status } ` ,
133+ `HTTP ${ response . status } : ${ errorText } ` ,
113134 response . status ,
114135 ) ;
115136 }
116137 } ,
117- catch : ( error ) =>
118- error instanceof HttpTrackerError
138+ catch : ( error ) => {
139+ return error instanceof HttpTrackerError
119140 ? error
120- : new HttpTrackerError ( "send" , error ) ,
141+ : new HttpTrackerError ( "send" , error ) ;
142+ } ,
121143 } ) . pipe (
122144 Effect . retry (
123145 Schedule . exponential ( Duration . millis ( cfg . retry . initialDelayMs ) ) . pipe (
124146 Schedule . compose ( Schedule . recurs ( cfg . retry . maxAttempts ) ) ,
125147 ) ,
126148 ) ,
127- // Don't fail on tracking error - just log and continue
149+ // Don't fail on tracking error - silently continue
128150 Effect . catchAll ( ( ) => Effect . void ) ,
129151 ) ;
130152 } ;
131153
132154 // Flush current buffer
133155 const flush = ( ) : Effect . Effect < void > =>
134- Ref . getAndSet ( buffer , [ ] ) . pipe ( Effect . flatMap ( sendBatch ) ) ;
156+ Effect . gen ( function * ( ) {
157+ const events = yield * Ref . getAndSet ( buffer , [ ] ) ;
158+ yield * sendBatch ( events ) ;
159+ } ) ;
135160
136161 const service : EventTrackerService < E > = {
137162 emit : ( event ) =>
138163 Effect . gen ( function * ( ) {
139164 yield * Ref . update ( buffer , ( events ) => [ ...events , event ] ) ;
165+ const current = yield * Ref . get ( buffer ) ;
140166
141167 // Check if we should flush
142- const current = yield * Ref . get ( buffer ) ;
143168 if ( current . length >= cfg . batchSize ) {
144169 yield * flush ( ) ;
145170 }
0 commit comments