@@ -18,6 +18,41 @@ import {
1818 createErrorFromResponse ,
1919} from './errors' ;
2020
21+ /**
22+ * Connection state for monitoring
23+ */
24+ export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error' ;
25+
26+ /**
27+ * Connection state change event
28+ */
29+ export interface ConnectionStateEvent {
30+ state : ConnectionState ;
31+ timestamp : number ;
32+ error ?: Error ;
33+ }
34+
35+ /**
36+ * Batch operation progress event
37+ */
38+ export interface BatchProgressEvent {
39+ operation : 'create' | 'update' | 'delete' ;
40+ total : number ;
41+ completed : number ;
42+ failed : number ;
43+ percentage : number ;
44+ }
45+
46+ /**
47+ * Event listener type for connection state changes
48+ */
49+ export type ConnectionStateListener = ( event : ConnectionStateEvent ) => void ;
50+
51+ /**
52+ * Event listener type for batch operation progress
53+ */
54+ export type BatchProgressListener = ( event : BatchProgressEvent ) => void ;
55+
2156/**
2257 * ObjectStack Data Source Adapter
2358 *
@@ -31,7 +66,14 @@ import {
3166 *
3267 * const dataSource = new ObjectStackAdapter({
3368 * baseUrl: 'https://api.example.com',
34- * token: 'your-api-token'
69+ * token: 'your-api-token',
70+ * autoReconnect: true,
71+ * maxReconnectAttempts: 5
72+ * });
73+ *
74+ * // Monitor connection state
75+ * dataSource.onConnectionStateChange((event) => {
76+ * console.log('Connection state:', event.state);
3577 * });
3678 *
3779 * const users = await dataSource.find('users', {
@@ -44,6 +86,13 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
4486 private client : ObjectStackClient ;
4587 private connected : boolean = false ;
4688 private metadataCache : MetadataCache ;
89+ private connectionState : ConnectionState = 'disconnected' ;
90+ private connectionStateListeners : ConnectionStateListener [ ] = [ ] ;
91+ private batchProgressListeners : BatchProgressListener [ ] = [ ] ;
92+ private autoReconnect : boolean ;
93+ private maxReconnectAttempts : number ;
94+ private reconnectDelay : number ;
95+ private reconnectAttempts : number = 0 ;
4796
4897 constructor ( config : {
4998 baseUrl : string ;
@@ -53,9 +102,15 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
53102 maxSize ?: number ;
54103 ttl ?: number ;
55104 } ;
105+ autoReconnect ?: boolean ;
106+ maxReconnectAttempts ?: number ;
107+ reconnectDelay ?: number ;
56108 } ) {
57109 this . client = new ObjectStackClient ( config ) ;
58110 this . metadataCache = new MetadataCache ( config . cache ) ;
111+ this . autoReconnect = config . autoReconnect ?? true ;
112+ this . maxReconnectAttempts = config . maxReconnectAttempts ?? 3 ;
113+ this . reconnectDelay = config . reconnectDelay ?? 1000 ;
59114 }
60115
61116 /**
@@ -64,20 +119,127 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
64119 */
65120 async connect ( ) : Promise < void > {
66121 if ( ! this . connected ) {
122+ this . setConnectionState ( 'connecting' ) ;
123+
67124 try {
68125 await this . client . connect ( ) ;
69126 this . connected = true ;
127+ this . reconnectAttempts = 0 ;
128+ this . setConnectionState ( 'connected' ) ;
70129 } catch ( error : unknown ) {
71130 const errorMessage = error instanceof Error ? error . message : 'Failed to connect to ObjectStack server' ;
72- throw new ConnectionError (
131+ const connectionError = new ConnectionError (
73132 errorMessage ,
74133 undefined ,
75134 { originalError : error }
76135 ) ;
136+
137+ this . setConnectionState ( 'error' , connectionError ) ;
138+
139+ // Attempt auto-reconnect if enabled
140+ if ( this . autoReconnect && this . reconnectAttempts < this . maxReconnectAttempts ) {
141+ await this . attemptReconnect ( ) ;
142+ } else {
143+ throw connectionError ;
144+ }
77145 }
78146 }
79147 }
80148
149+ /**
150+ * Attempt to reconnect to the server with exponential backoff
151+ */
152+ private async attemptReconnect ( ) : Promise < void > {
153+ this . reconnectAttempts ++ ;
154+ this . setConnectionState ( 'reconnecting' ) ;
155+
156+ // Exponential backoff: delay * 2^(attempts-1)
157+ const delay = this . reconnectDelay * Math . pow ( 2 , this . reconnectAttempts - 1 ) ;
158+
159+ await new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
160+
161+ this . connected = false ;
162+ await this . connect ( ) ;
163+ }
164+
165+ /**
166+ * Get the current connection state
167+ */
168+ getConnectionState ( ) : ConnectionState {
169+ return this . connectionState ;
170+ }
171+
172+ /**
173+ * Check if the adapter is currently connected
174+ */
175+ isConnected ( ) : boolean {
176+ return this . connected && this . connectionState === 'connected' ;
177+ }
178+
179+ /**
180+ * Register a listener for connection state changes
181+ */
182+ onConnectionStateChange ( listener : ConnectionStateListener ) : ( ) => void {
183+ this . connectionStateListeners . push ( listener ) ;
184+
185+ // Return unsubscribe function
186+ return ( ) => {
187+ const index = this . connectionStateListeners . indexOf ( listener ) ;
188+ if ( index > - 1 ) {
189+ this . connectionStateListeners . splice ( index , 1 ) ;
190+ }
191+ } ;
192+ }
193+
194+ /**
195+ * Register a listener for batch operation progress
196+ */
197+ onBatchProgress ( listener : BatchProgressListener ) : ( ) => void {
198+ this . batchProgressListeners . push ( listener ) ;
199+
200+ // Return unsubscribe function
201+ return ( ) => {
202+ const index = this . batchProgressListeners . indexOf ( listener ) ;
203+ if ( index > - 1 ) {
204+ this . batchProgressListeners . splice ( index , 1 ) ;
205+ }
206+ } ;
207+ }
208+
209+ /**
210+ * Set connection state and notify listeners
211+ */
212+ private setConnectionState ( state : ConnectionState , error ?: Error ) : void {
213+ this . connectionState = state ;
214+
215+ const event : ConnectionStateEvent = {
216+ state,
217+ timestamp : Date . now ( ) ,
218+ error,
219+ } ;
220+
221+ this . connectionStateListeners . forEach ( listener => {
222+ try {
223+ listener ( event ) ;
224+ } catch ( err ) {
225+ console . error ( 'Error in connection state listener:' , err ) ;
226+ }
227+ } ) ;
228+ }
229+
230+ /**
231+ * Emit batch progress event to listeners
232+ */
233+ private emitBatchProgress ( event : BatchProgressEvent ) : void {
234+ this . batchProgressListeners . forEach ( listener => {
235+ try {
236+ listener ( event ) ;
237+ } catch ( err ) {
238+ console . error ( 'Error in batch progress listener:' , err ) ;
239+ }
240+ } ) ;
241+ }
242+
81243 /**
82244 * Find multiple records with query parameters.
83245 * Converts OData-style params to ObjectStack query options.
@@ -155,6 +317,7 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
155317
156318 /**
157319 * Bulk operations with optimized batch processing and error handling.
320+ * Emits progress events for tracking operation status.
158321 *
159322 * @param resource - Resource name
160323 * @param operation - Operation type (create, update, delete)
@@ -168,10 +331,29 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
168331 return [ ] ;
169332 }
170333
334+ const total = data . length ;
335+ let completed = 0 ;
336+ let failed = 0 ;
337+
338+ const emitProgress = ( ) => {
339+ this . emitBatchProgress ( {
340+ operation,
341+ total,
342+ completed,
343+ failed,
344+ percentage : total > 0 ? ( completed + failed ) / total * 100 : 0 ,
345+ } ) ;
346+ } ;
347+
171348 try {
172349 switch ( operation ) {
173350 case 'create' :
174- return await this . client . data . createMany < T > ( resource , data ) ;
351+ emitProgress ( ) ;
352+ const created = await this . client . data . createMany < T > ( resource , data ) ;
353+ completed = created . length ;
354+ failed = total - completed ;
355+ emitProgress ( ) ;
356+ return created ;
175357
176358 case 'delete' : {
177359 const ids = data . map ( item => ( item as Record < string , unknown > ) . id ) . filter ( Boolean ) as string [ ] ;
@@ -183,10 +365,17 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
183365 error : `Missing ID for item at index ${ index } `
184366 } ) ) ;
185367
368+ failed = data . length ;
369+ emitProgress ( ) ;
370+
186371 throw new BulkOperationError ( 'delete' , 0 , data . length , errors ) ;
187372 }
188373
374+ emitProgress ( ) ;
189375 await this . client . data . deleteMany ( resource , ids ) ;
376+ completed = ids . length ;
377+ failed = total - completed ;
378+ emitProgress ( ) ;
190379 return [ ] as T [ ] ;
191380 }
192381
@@ -195,16 +384,21 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
195384 // eslint-disable-next-line @typescript-eslint/no-explicit-any
196385 if ( typeof ( this . client . data as any ) . updateMany === 'function' ) {
197386 try {
387+ emitProgress ( ) ;
198388 // eslint-disable-next-line @typescript-eslint/no-explicit-any
199389 const updateMany = ( this . client . data as any ) . updateMany ;
200- return await updateMany ( resource , data ) as T [ ] ;
390+ const updated = await updateMany ( resource , data ) as T [ ] ;
391+ completed = updated . length ;
392+ failed = total - completed ;
393+ emitProgress ( ) ;
394+ return updated ;
201395 } catch {
202396 // If updateMany is not supported, fall back to individual updates
203397 // Silently fallback without logging
204398 }
205399 }
206400
207- // Fallback: Process updates individually with detailed error tracking
401+ // Fallback: Process updates individually with detailed error tracking and progress
208402 const results : T [ ] = [ ] ;
209403 const errors : Array < { index : number ; error : unknown } > = [ ] ;
210404
@@ -214,15 +408,21 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
214408
215409 if ( ! id ) {
216410 errors . push ( { index : i , error : 'Missing ID' } ) ;
411+ failed ++ ;
412+ emitProgress ( ) ;
217413 continue ;
218414 }
219415
220416 try {
221417 const result = await this . client . data . update < T > ( resource , String ( id ) , item ) ;
222418 results . push ( result ) ;
419+ completed ++ ;
420+ emitProgress ( ) ;
223421 } catch ( error : unknown ) {
224422 const errorMessage = error instanceof Error ? error . message : String ( error ) ;
225423 errors . push ( { index : i , error : errorMessage } ) ;
424+ failed ++ ;
425+ emitProgress ( ) ;
226426 }
227427 }
228428
@@ -248,6 +448,9 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
248448 ) ;
249449 }
250450 } catch ( error : unknown ) {
451+ // Emit final progress with failure
452+ emitProgress ( ) ;
453+
251454 // If it's already a BulkOperationError, re-throw it
252455 if ( error instanceof BulkOperationError ) {
253456 throw error ;
@@ -393,7 +596,9 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
393596 * const dataSource = createObjectStackAdapter({
394597 * baseUrl: process.env.API_URL,
395598 * token: process.env.API_TOKEN,
396- * cache: { maxSize: 100, ttl: 300000 }
599+ * cache: { maxSize: 100, ttl: 300000 },
600+ * autoReconnect: true,
601+ * maxReconnectAttempts: 5
397602 * });
398603 * ```
399604 */
@@ -405,6 +610,9 @@ export function createObjectStackAdapter<T = unknown>(config: {
405610 maxSize ?: number ;
406611 ttl ?: number ;
407612 } ;
613+ autoReconnect ?: boolean ;
614+ maxReconnectAttempts ?: number ;
615+ reconnectDelay ?: number ;
408616} ) : DataSource < T > {
409617 return new ObjectStackAdapter < T > ( config ) ;
410618}
0 commit comments