@@ -20,6 +20,7 @@ export type S2MetadataStreamOptions<T = any> = {
2020 signal ?: AbortSignal ;
2121 flushIntervalMs ?: number ; // How often to flush batched chunks (default 200ms)
2222 maxRetries ?: number ; // Max number of retries for failed flushes (default 10)
23+ debug ?: boolean ; // Enable debug logging (default false)
2324} ;
2425
2526/**
@@ -30,6 +31,7 @@ export type S2MetadataStreamOptions<T = any> = {
3031 * - Periodic flushing: Flushes buffered chunks every ~200ms (configurable)
3132 * - Sequential writes: Uses p-limit to ensure writes happen in order
3233 * - Automatic retries: Retries failed writes with exponential backoff
34+ * - Debug logging: Enable with debug: true to see detailed operation logs
3335 *
3436 * Example usage:
3537 * ```typescript
@@ -39,6 +41,7 @@ export type S2MetadataStreamOptions<T = any> = {
3941 * accessToken: "s2-token-here",
4042 * source: myAsyncIterable,
4143 * flushIntervalMs: 200, // Optional: flush every 200ms
44+ * debug: true, // Optional: enable debug logging
4245 * });
4346 *
4447 * // Wait for streaming to complete
@@ -57,6 +60,7 @@ export class S2MetadataStream<T = any> implements StreamInstance {
5760 private streamPromise : Promise < void > ;
5861 private readonly flushIntervalMs : number ;
5962 private readonly maxRetries : number ;
63+ private readonly debug : boolean ;
6064
6165 // Buffering state
6266 private streamComplete = false ;
@@ -74,11 +78,16 @@ export class S2MetadataStream<T = any> implements StreamInstance {
7478
7579 constructor ( private options : S2MetadataStreamOptions < T > ) {
7680 this . limiter = options . limiter ( 1 ) ;
81+ this . debug = options . debug ?? false ;
7782
7883 this . s2Client = new S2 ( { accessToken : options . accessToken } ) ;
7984 this . flushIntervalMs = options . flushIntervalMs ?? 200 ;
8085 this . maxRetries = options . maxRetries ?? 10 ;
8186
87+ this . log (
88+ `[S2MetadataStream] Initializing: basin=${ options . basin } , stream=${ options . stream } , flushIntervalMs=${ this . flushIntervalMs } , maxRetries=${ this . maxRetries } `
89+ ) ;
90+
8291 const [ serverStream , consumerStream ] = this . createTeeStreams ( ) ;
8392 this . serverStream = serverStream ;
8493 this . consumerStream = consumerStream ;
@@ -105,7 +114,6 @@ export class S2MetadataStream<T = any> implements StreamInstance {
105114
106115 controller . close ( ) ;
107116 } catch ( error ) {
108- console . error ( "[S2MetadataStream] Error reading from source" , error ) ;
109117 controller . error ( error ) ;
110118 }
111119 } ,
@@ -115,6 +123,7 @@ export class S2MetadataStream<T = any> implements StreamInstance {
115123 }
116124
117125 private startBuffering ( ) : void {
126+ this . log ( "[S2MetadataStream] Starting buffering task" ) ;
118127 this . streamReader = this . serverStream . getReader ( ) ;
119128
120129 this . bufferReaderTask = ( async ( ) => {
@@ -126,20 +135,29 @@ export class S2MetadataStream<T = any> implements StreamInstance {
126135
127136 if ( done ) {
128137 this . streamComplete = true ;
138+ this . log ( `[S2MetadataStream] Stream complete after ${ chunkCount } chunks` ) ;
129139 break ;
130140 }
131141
132142 // Add to pending flushes
133143 this . pendingFlushes . push ( value ) ;
134144 chunkCount ++ ;
145+
146+ if ( chunkCount % 100 === 0 ) {
147+ this . log (
148+ `[S2MetadataStream] Buffered ${ chunkCount } chunks, pending flushes: ${ this . pendingFlushes . length } `
149+ ) ;
150+ }
135151 }
136152 } catch ( error ) {
153+ this . logError ( "[S2MetadataStream] Error in buffering task:" , error ) ;
137154 throw error ;
138155 }
139156 } ) ( ) ;
140157 }
141158
142159 private startPeriodicFlush ( ) : void {
160+ this . log ( `[S2MetadataStream] Starting periodic flush (every ${ this . flushIntervalMs } ms)` ) ;
143161 this . flushInterval = setInterval ( ( ) => {
144162 this . flush ( ) . catch ( ( ) => {
145163 // Errors are already logged in flush()
@@ -154,10 +172,10 @@ export class S2MetadataStream<T = any> implements StreamInstance {
154172
155173 // Take all pending chunks
156174 const chunksToFlush = this . pendingFlushes . splice ( 0 ) ;
175+ this . log ( `[S2MetadataStream] Flushing ${ chunksToFlush . length } chunks to S2` ) ;
157176
158177 // Add flush to limiter queue to ensure sequential execution
159178 const flushPromise = this . limiter ( async ( ) => {
160- const startTime = Date . now ( ) ;
161179 try {
162180 // Convert chunks to S2 record format (body as JSON string)
163181 const records = chunksToFlush . map ( ( data ) => ( {
@@ -170,32 +188,31 @@ export class S2MetadataStream<T = any> implements StreamInstance {
170188 appendInput : { records } ,
171189 } ) ;
172190
173- const duration = Date . now ( ) - startTime ;
191+ this . log ( `[S2MetadataStream] Successfully flushed ${ chunksToFlush . length } chunks` ) ;
174192
175193 // Reset retry count on success
176194 this . retryCount = 0 ;
177195 } catch ( error ) {
178- console . error ( "[S2MetadataStream] Flush error" , {
179- error,
180- count : chunksToFlush . length ,
181- retryCount : this . retryCount ,
182- } ) ;
183-
184196 // Handle retryable errors
185197 if ( this . isRetryableError ( error ) && this . retryCount < this . maxRetries ) {
186198 this . retryCount ++ ;
187199 const delayMs = this . calculateBackoffDelay ( ) ;
188200
201+ this . logError (
202+ `[S2MetadataStream] Flush failed (attempt ${ this . retryCount } /${ this . maxRetries } ), retrying in ${ delayMs } ms:` ,
203+ error
204+ ) ;
205+
189206 await this . delay ( delayMs ) ;
190207
191208 // Re-add chunks to pending flushes and retry
192209 this . pendingFlushes . unshift ( ...chunksToFlush ) ;
193210 await this . flush ( ) ;
194211 } else {
195- console . error ( "[S2MetadataStream] Max retries exceeded or non-retryable error" , {
196- retryCount : this . retryCount ,
197- maxRetries : this . maxRetries ,
198- } ) ;
212+ this . logError (
213+ `[S2MetadataStream] Flush failed permanently after ${ this . retryCount } retries:` ,
214+ error
215+ ) ;
199216 throw error ;
200217 }
201218 }
@@ -205,20 +222,28 @@ export class S2MetadataStream<T = any> implements StreamInstance {
205222 }
206223
207224 private async initializeServerStream ( ) : Promise < void > {
225+ this . log ( "[S2MetadataStream] Waiting for buffer task to complete" ) ;
208226 // Wait for buffer task and all flushes to complete
209227 await this . bufferReaderTask ;
210228
229+ this . log (
230+ `[S2MetadataStream] Buffer task complete, performing final flush (${ this . pendingFlushes . length } pending chunks)`
231+ ) ;
211232 // Final flush
212233 await this . flush ( ) ;
213234
235+ this . log ( `[S2MetadataStream] Waiting for ${ this . flushPromises . length } flush promises` ) ;
214236 // Wait for all pending flushes
215237 await Promise . all ( this . flushPromises ) ;
216238
239+ this . log ( "[S2MetadataStream] All flushes complete, cleaning up" ) ;
217240 // Clean up
218241 if ( this . flushInterval ) {
219242 clearInterval ( this . flushInterval ) ;
220243 this . flushInterval = null ;
221244 }
245+
246+ this . log ( "[S2MetadataStream] Stream completed successfully" ) ;
222247 }
223248
224249 public async wait ( ) : Promise < void > {
@@ -231,6 +256,18 @@ export class S2MetadataStream<T = any> implements StreamInstance {
231256
232257 // Helper methods
233258
259+ private log ( message : string ) : void {
260+ if ( this . debug ) {
261+ console . log ( message ) ;
262+ }
263+ }
264+
265+ private logError ( message : string , error ?: any ) : void {
266+ if ( this . debug ) {
267+ console . error ( message , error ) ;
268+ }
269+ }
270+
234271 private isRetryableError ( error : any ) : boolean {
235272 if ( ! error ) return false ;
236273
0 commit comments