@finatic/client 0.0.140 → 0.0.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,332 @@
1
+ import { Logger, LoggerExtra, LoggerMetadata, LoggerOptions, LogLevel, LogVerbosity } from './logger.types';
2
+
3
+ const LOG_LEVEL_ORDER: Record<Exclude<LogLevel, 'silent'>, number> = {
4
+ error: 0,
5
+ warn: 1,
6
+ info: 2,
7
+ debug: 3,
8
+ };
9
+
10
+ const LEVEL_TO_CONSOLE: Record<Exclude<LogLevel, 'silent'>, keyof Console> = {
11
+ error: 'error',
12
+ warn: 'warn',
13
+ info: 'info',
14
+ debug: 'debug',
15
+ };
16
+
17
+ const DEFAULT_LOGGER_NAME = 'FinaticLogger';
18
+
19
+ const parseLogLevel = (value: unknown, fallback: LogLevel): LogLevel => {
20
+ if (typeof value !== 'string') {
21
+ return fallback;
22
+ }
23
+
24
+ const normalized = value.toLowerCase().trim() as LogLevel;
25
+ if (normalized === 'silent' || normalized === 'error' || normalized === 'warn' || normalized === 'info' || normalized === 'debug') {
26
+ return normalized;
27
+ }
28
+
29
+ return fallback;
30
+ };
31
+
32
+ const parseVerbosity = (value: unknown, fallback: LogVerbosity): LogVerbosity => {
33
+ if (typeof value !== 'string' && typeof value !== 'number') {
34
+ return fallback;
35
+ }
36
+
37
+ const numeric = typeof value === 'number' ? value : Number.parseInt(value, 10);
38
+ if (Number.isNaN(numeric)) {
39
+ return fallback;
40
+ }
41
+
42
+ if (numeric <= 0) {
43
+ return 0;
44
+ }
45
+
46
+ if (numeric >= 3) {
47
+ return 3;
48
+ }
49
+
50
+ return numeric as LogVerbosity;
51
+ };
52
+
53
+ const resolveEnv = (key: string): string | undefined => {
54
+ try {
55
+ if (typeof process !== 'undefined' && process.env && typeof process.env[key] === 'string') {
56
+ return process.env[key];
57
+ }
58
+ } catch {
59
+ // ignore
60
+ }
61
+
62
+ try {
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ const metaEnv = typeof import.meta !== 'undefined' ? (import.meta as any).env : undefined;
65
+ if (metaEnv && typeof metaEnv[key] === 'string') {
66
+ return metaEnv[key];
67
+ }
68
+ } catch {
69
+ // ignore
70
+ }
71
+
72
+ try {
73
+ if (typeof globalThis !== 'undefined') {
74
+ const value = (globalThis as Record<string, unknown>)[key];
75
+ if (typeof value === 'string') {
76
+ return value;
77
+ }
78
+ }
79
+ } catch {
80
+ // ignore
81
+ }
82
+
83
+ return undefined;
84
+ };
85
+
86
+ const resolveDefaultLogLevel = (explicitLevel?: LogLevel): LogLevel => {
87
+ if (explicitLevel) {
88
+ return explicitLevel;
89
+ }
90
+
91
+ const envLevel =
92
+ resolveEnv('FINATIC_LOG_LEVEL') ||
93
+ resolveEnv('VITE_FINATIC_LOG_LEVEL') ||
94
+ resolveEnv('NEXT_PUBLIC_FINATIC_LOG_LEVEL') ||
95
+ resolveEnv('NEXT_FINATIC_LOG_LEVEL') ||
96
+ resolveEnv('REACT_APP_FINATIC_LOG_LEVEL') ||
97
+ resolveEnv('NUXT_PUBLIC_FINATIC_LOG_LEVEL') ||
98
+ resolveEnv('NX_FINATIC_LOG_LEVEL');
99
+
100
+ if (envLevel) {
101
+ return parseLogLevel(envLevel, 'silent');
102
+ }
103
+
104
+ return 'silent';
105
+ };
106
+
107
+ const resolveVerbosity = (): LogVerbosity => {
108
+ const envVerbosity =
109
+ resolveEnv('FINATIC_LOG_VERBOSITY') ||
110
+ resolveEnv('VITE_FINATIC_LOG_VERBOSITY') ||
111
+ resolveEnv('NEXT_PUBLIC_FINATIC_LOG_VERBOSITY') ||
112
+ resolveEnv('NEXT_FINATIC_LOG_VERBOSITY') ||
113
+ resolveEnv('REACT_APP_FINATIC_LOG_VERBOSITY') ||
114
+ resolveEnv('NUXT_PUBLIC_FINATIC_LOG_VERBOSITY') ||
115
+ resolveEnv('NX_FINATIC_LOG_VERBOSITY');
116
+
117
+ if (envVerbosity) {
118
+ return parseVerbosity(envVerbosity, 1);
119
+ }
120
+
121
+ return 1;
122
+ };
123
+
124
+ const resolveBaseMetadata = (): LoggerMetadata => {
125
+ const base: LoggerMetadata = {
126
+ timestamp: new Date().toISOString(),
127
+ };
128
+
129
+ try {
130
+ if (typeof globalThis !== 'undefined') {
131
+ if (typeof (globalThis as Window).location !== 'undefined') {
132
+ base.host = (globalThis as Window).location.hostname;
133
+ }
134
+ if (typeof (globalThis as Window).navigator !== 'undefined') {
135
+ base.user_agent = (globalThis as Window).navigator.userAgent;
136
+ }
137
+ }
138
+ } catch {
139
+ // ignore
140
+ }
141
+
142
+ try {
143
+ if (typeof process !== 'undefined') {
144
+ base.pid = process.pid;
145
+ }
146
+ } catch {
147
+ // ignore
148
+ }
149
+
150
+ return base;
151
+ };
152
+
153
+ const normalizeError = (error: unknown): Record<string, unknown> | undefined => {
154
+ if (!error) {
155
+ return undefined;
156
+ }
157
+
158
+ if (error instanceof Error) {
159
+ return {
160
+ type: error.name,
161
+ message: error.message,
162
+ stacktrace: error.stack,
163
+ };
164
+ }
165
+
166
+ if (typeof error === 'object') {
167
+ return { ...(error as Record<string, unknown>) };
168
+ }
169
+
170
+ return {
171
+ type: 'Error',
172
+ message: String(error),
173
+ };
174
+ };
175
+
176
+ const shouldLog = (requestedLevel: Exclude<LogLevel, 'silent'>, currentLevel: LogLevel): boolean => {
177
+ if (currentLevel === 'silent') {
178
+ return false;
179
+ }
180
+
181
+ const currentOrder = LOG_LEVEL_ORDER[currentLevel];
182
+ const requestedOrder = LOG_LEVEL_ORDER[requestedLevel];
183
+
184
+ return requestedOrder <= currentOrder;
185
+ };
186
+
187
+ const buildPayload = (
188
+ name: string,
189
+ level: Exclude<LogLevel, 'silent'>,
190
+ message: string,
191
+ defaultMetadata: LoggerMetadata | undefined,
192
+ extra: LoggerExtra | undefined,
193
+ verbosity: LogVerbosity,
194
+ ): Record<string, unknown> => {
195
+ const payload: Record<string, unknown> = {
196
+ timestamp: new Date().toISOString(),
197
+ message,
198
+ };
199
+
200
+ if (verbosity >= 1 && extra) {
201
+ if (extra.module) {
202
+ payload.module = extra.module;
203
+ }
204
+ if (extra.function) {
205
+ payload.function = extra.function;
206
+ }
207
+ }
208
+
209
+ if (verbosity >= 2) {
210
+ if (extra?.duration_ms !== undefined) {
211
+ payload.duration_ms = extra.duration_ms;
212
+ }
213
+ if (extra?.event) {
214
+ payload.event = extra.event;
215
+ }
216
+ if (extra?.error) {
217
+ payload.error = normalizeError(extra.error);
218
+ }
219
+ }
220
+
221
+ if (verbosity >= 3) {
222
+ payload.level = level.toUpperCase();
223
+ payload.name = name;
224
+ const baseMetadata = resolveBaseMetadata();
225
+ const mergedMetadata: LoggerMetadata = {
226
+ ...baseMetadata,
227
+ ...(defaultMetadata || {}),
228
+ ...(extra?.metadata || {}),
229
+ };
230
+ if (Object.keys(mergedMetadata).length > 0) {
231
+ payload.metadata = mergedMetadata;
232
+ }
233
+ }
234
+
235
+ const restKeys = ['module', 'function', 'event', 'duration_ms', 'error', 'metadata'];
236
+ if (extra) {
237
+ Object.entries(extra).forEach(([key, value]) => {
238
+ if (!restKeys.includes(key) && value !== undefined) {
239
+ payload[key] = value;
240
+ }
241
+ });
242
+ }
243
+
244
+ return payload;
245
+ };
246
+
247
+ const consoleWrite = (
248
+ consoleLevel: keyof Console,
249
+ name: string,
250
+ level: Exclude<LogLevel, 'silent'>,
251
+ message: string,
252
+ payload: Record<string, unknown>,
253
+ ): void => {
254
+ if (typeof console === 'undefined' || typeof console[consoleLevel] !== 'function') {
255
+ return;
256
+ }
257
+
258
+ const prefix = `${level.toUpperCase()}: ${name} || ${message}`;
259
+ console[consoleLevel](prefix, payload);
260
+ };
261
+
262
+ export const setupLogger = (nameOrOptions: string | LoggerOptions, level?: LogLevel, defaultMetadata?: LoggerMetadata): Logger => {
263
+ const options: LoggerOptions =
264
+ typeof nameOrOptions === 'string'
265
+ ? {
266
+ name: nameOrOptions,
267
+ level,
268
+ defaultMetadata,
269
+ }
270
+ : nameOrOptions;
271
+
272
+ const loggerName = options.name || DEFAULT_LOGGER_NAME;
273
+ let currentLevel: LogLevel = resolveDefaultLogLevel(options.level);
274
+ const loggerDefaultMetadata = options.defaultMetadata;
275
+ const verbosity = resolveVerbosity();
276
+
277
+ const log = (requestedLevel: Exclude<LogLevel, 'silent'>, message: string, extra?: LoggerExtra) => {
278
+ if (!shouldLog(requestedLevel, currentLevel)) {
279
+ return;
280
+ }
281
+
282
+ const payload = buildPayload(loggerName, requestedLevel, message, loggerDefaultMetadata, extra, verbosity);
283
+ consoleWrite(LEVEL_TO_CONSOLE[requestedLevel], loggerName, requestedLevel, message, payload);
284
+ };
285
+
286
+ return {
287
+ getLevel: () => currentLevel,
288
+ setLevel: (nextLevel: LogLevel) => {
289
+ currentLevel = nextLevel;
290
+ },
291
+ debug: (message: string, extra?: LoggerExtra) => log('debug', message, extra),
292
+ info: (message: string, extra?: LoggerExtra) => log('info', message, extra),
293
+ warn: (message: string, extra?: LoggerExtra) => log('warn', message, extra),
294
+ error: (message: string, extra?: LoggerExtra) => log('error', message, extra),
295
+ exception: (message: string, error: unknown, extra?: LoggerExtra) => {
296
+ log('error', message, {
297
+ ...extra,
298
+ error,
299
+ event: extra?.event || 'exception',
300
+ });
301
+ },
302
+ };
303
+ };
304
+
305
+ export const buildLoggerExtra = (metadata: LoggerMetadata): LoggerExtra => ({
306
+ metadata,
307
+ });
308
+
309
+ export const logStartEnd =
310
+ (logger: Logger) =>
311
+ <Args extends unknown[], ReturnType>(fn: (...args: Args) => ReturnType | Promise<ReturnType>) =>
312
+ async (...args: Args): Promise<ReturnType> => {
313
+ const start = Date.now();
314
+ const functionName = fn.name || 'anonymous';
315
+ logger.debug('START', { module: 'logStartEnd', function: functionName, event: 'start' });
316
+
317
+ try {
318
+ const result = await fn(...args);
319
+ const duration = Date.now() - start;
320
+ logger.info('END', { module: 'logStartEnd', function: functionName, event: 'end', duration_ms: duration });
321
+ return result;
322
+ } catch (error) {
323
+ const duration = Date.now() - start;
324
+ logger.exception('EXCEPTION', error, {
325
+ module: 'logStartEnd',
326
+ function: functionName,
327
+ duration_ms: duration,
328
+ });
329
+ throw error;
330
+ }
331
+ };
332
+
@@ -0,0 +1,34 @@
1
+ export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';
2
+
3
+ export interface LoggerMetadata {
4
+ [key: string]: unknown;
5
+ }
6
+
7
+ export interface LoggerExtra {
8
+ metadata?: LoggerMetadata;
9
+ module?: string;
10
+ function?: string;
11
+ event?: string;
12
+ duration_ms?: number;
13
+ error?: unknown;
14
+ [key: string]: unknown;
15
+ }
16
+
17
+ export interface LoggerOptions {
18
+ name: string;
19
+ level?: LogLevel;
20
+ defaultMetadata?: LoggerMetadata;
21
+ }
22
+
23
+ export interface Logger {
24
+ getLevel: () => LogLevel;
25
+ setLevel: (level: LogLevel) => void;
26
+ debug: (message: string, extra?: LoggerExtra) => void;
27
+ info: (message: string, extra?: LoggerExtra) => void;
28
+ warn: (message: string, extra?: LoggerExtra) => void;
29
+ error: (message: string, extra?: LoggerExtra) => void;
30
+ exception: (message: string, error: unknown, extra?: LoggerExtra) => void;
31
+ }
32
+
33
+ export type LogVerbosity = 0 | 1 | 2 | 3;
34
+
@@ -47,6 +47,11 @@ import {
47
47
  OrderValidationError,
48
48
  } from '../utils/errors';
49
49
  import { MockDataProvider, MockConfig } from './MockDataProvider';
50
+ import { setupLogger, buildLoggerExtra, LoggerExtra } from '../lib/logger';
51
+
52
+ const mockApiLogger = setupLogger('FinaticClientSDK.MockApiClient', undefined, {
53
+ codebase: 'FinaticClientSDK',
54
+ });
50
55
 
51
56
  /**
52
57
  * Mock API Client that implements the same interface as the real ApiClient
@@ -71,6 +76,15 @@ export class MockApiClient {
71
76
  // Mock data provider
72
77
  private mockDataProvider: MockDataProvider;
73
78
  private readonly mockApiOnly: boolean;
79
+ private readonly logger = mockApiLogger;
80
+
81
+ private buildLoggerExtra(functionName: string, metadata?: Record<string, unknown>): LoggerExtra {
82
+ return {
83
+ module: 'MockApiClient',
84
+ function: functionName,
85
+ ...(metadata ? buildLoggerExtra(metadata) : {}),
86
+ };
87
+ }
74
88
 
75
89
  constructor(baseUrl: string, deviceInfo?: DeviceInfo, mockConfig?: MockConfig) {
76
90
  this.baseUrl = baseUrl;
@@ -80,9 +94,13 @@ export class MockApiClient {
80
94
 
81
95
  // Log that mocks are being used
82
96
  if (this.mockApiOnly) {
83
- console.log('🔧 Finatic SDK: Using MOCK API Client (API only - real portal)');
97
+ this.logger.info('Using mock API client (API only, real portal)', this.buildLoggerExtra('constructor', {
98
+ mock_api_only: true,
99
+ }));
84
100
  } else {
85
- console.log('🔧 Finatic SDK: Using MOCK API Client');
101
+ this.logger.info('Using mock API client', this.buildLoggerExtra('constructor', {
102
+ mock_api_only: false,
103
+ }));
86
104
  }
87
105
  }
88
106
 
@@ -339,14 +357,12 @@ export class MockApiClient {
339
357
  const accessToken = await this.getValidAccessToken();
340
358
 
341
359
  // Debug logging
342
- console.log('MockApiClient.placeBrokerOrder Debug:', {
343
- params,
344
- tradingContext: this.tradingContext,
345
- paramsBroker: params.broker,
346
- contextBroker: this.tradingContext.broker,
347
- paramsAccountNumber: params.accountNumber,
348
- contextAccountNumber: this.tradingContext.accountNumber,
349
- });
360
+ this.logger.debug('placeBrokerOrder parameters', this.buildLoggerExtra('placeBrokerOrder', {
361
+ has_params_broker: Boolean(params.broker),
362
+ has_context_broker: Boolean(this.tradingContext.broker),
363
+ has_params_account: Boolean(params.accountNumber),
364
+ has_context_account: Boolean(this.tradingContext.accountNumber),
365
+ }));
350
366
 
351
367
  const fullParams: BrokerOrderParams = {
352
368
  broker:
@@ -374,7 +390,13 @@ export class MockApiClient {
374
390
  order_id: params.order_id,
375
391
  };
376
392
 
377
- console.log('MockApiClient.placeBrokerOrder Debug - Final params:', fullParams);
393
+ this.logger.debug('placeBrokerOrder normalized parameters', this.buildLoggerExtra('placeBrokerOrder', {
394
+ broker: fullParams.broker,
395
+ account_number_present: Boolean(fullParams.accountNumber),
396
+ symbol: fullParams.symbol,
397
+ order_type: fullParams.orderType,
398
+ asset_type: fullParams.assetType,
399
+ }));
378
400
  return this.mockDataProvider.mockPlaceOrder(fullParams);
379
401
  }
380
402
 
@@ -427,14 +449,18 @@ export class MockApiClient {
427
449
  }
428
450
 
429
451
  setAccount(accountNumber: string, accountId?: string): void {
430
- console.log('MockApiClient.setAccount Debug:', {
431
- accountNumber,
432
- accountId,
433
- previousContext: { ...this.tradingContext },
434
- });
452
+ this.logger.debug('setAccount invoked', this.buildLoggerExtra('setAccount', {
453
+ account_number: accountNumber,
454
+ account_id_present: Boolean(accountId),
455
+ had_existing_account: Boolean(this.tradingContext.accountNumber),
456
+ }));
435
457
  this.tradingContext.accountNumber = accountNumber;
436
458
  this.tradingContext.accountId = accountId;
437
- console.log('MockApiClient.setAccount Debug - Updated context:', this.tradingContext);
459
+ this.logger.debug('setAccount updated context', this.buildLoggerExtra('setAccount', {
460
+ broker: this.tradingContext.broker,
461
+ account_number_present: Boolean(this.tradingContext.accountNumber),
462
+ account_id_present: Boolean(this.tradingContext.accountId),
463
+ }));
438
464
  }
439
465
 
440
466
 
@@ -327,4 +327,135 @@ export interface DisconnectCompanyResponse {
327
327
  };
328
328
  message: string;
329
329
  status_code: number;
330
+ }
331
+
332
+ // Order detail types
333
+ export interface OrderFill {
334
+ id: string;
335
+ order_id: string;
336
+ leg_id: string | null;
337
+ price: number;
338
+ quantity: number;
339
+ executed_at: string;
340
+ execution_id: string | null;
341
+ trade_id: string | null;
342
+ venue: string | null;
343
+ commission_fee: number | null;
344
+ created_at: string;
345
+ updated_at: string;
346
+ }
347
+
348
+ export interface OrderEvent {
349
+ id: string;
350
+ order_id: string;
351
+ order_group_id: string | null;
352
+ event_type: string | null;
353
+ event_time: string;
354
+ event_id: string | null;
355
+ order_status: string | null;
356
+ inferred: boolean;
357
+ confidence: number | null;
358
+ reason_code: string | null;
359
+ recorded_at: string | null;
360
+ }
361
+
362
+ export interface OrderLeg {
363
+ id: string;
364
+ order_id: string;
365
+ leg_index: number;
366
+ asset_type: string;
367
+ broker_provided_symbol: string | null;
368
+ quantity: number;
369
+ filled_quantity: number | null;
370
+ avg_fill_price: number | null;
371
+ created_at: string | null;
372
+ updated_at: string | null;
373
+ }
374
+
375
+ export interface OrderGroupOrder extends BrokerDataOrder {
376
+ legs: OrderLeg[];
377
+ }
378
+
379
+ export interface OrderGroup {
380
+ id: string;
381
+ user_broker_connection_id: string | null;
382
+ created_at: string;
383
+ updated_at: string;
384
+ orders: OrderGroupOrder[];
385
+ }
386
+
387
+ // Position detail types
388
+ export interface PositionLot {
389
+ id: string;
390
+ position_id: string | null;
391
+ user_broker_connection_id: string;
392
+ broker_provided_account_id: string;
393
+ instrument_key: string;
394
+ asset_type: string | null;
395
+ side: 'long' | 'short' | null;
396
+ open_quantity: number;
397
+ closed_quantity: number;
398
+ remaining_quantity: number;
399
+ open_price: number;
400
+ close_price_avg: number | null;
401
+ cost_basis: number;
402
+ cost_basis_w_commission: number;
403
+ realized_pl: number;
404
+ realized_pl_w_commission: number;
405
+ lot_opened_at: string;
406
+ lot_closed_at: string | null;
407
+ position_group_id: string | null;
408
+ created_at: string;
409
+ updated_at: string;
410
+ position_lot_fills?: PositionLotFill[];
411
+ }
412
+
413
+ export interface PositionLotFill {
414
+ id: string;
415
+ lot_id: string;
416
+ order_fill_id: string;
417
+ fill_price: number;
418
+ fill_quantity: number;
419
+ executed_at: string;
420
+ commission_share: number | null;
421
+ created_at: string;
422
+ updated_at: string;
423
+ }
424
+
425
+ // Filter types for detail endpoints
426
+ export interface OrderFillsFilter {
427
+ connection_id?: string;
428
+ limit?: number;
429
+ offset?: number;
430
+ }
431
+
432
+ export interface OrderEventsFilter {
433
+ connection_id?: string;
434
+ limit?: number;
435
+ offset?: number;
436
+ }
437
+
438
+ export interface OrderGroupsFilter {
439
+ broker_id?: string;
440
+ connection_id?: string;
441
+ limit?: number;
442
+ offset?: number;
443
+ created_after?: string;
444
+ created_before?: string;
445
+ }
446
+
447
+ export interface PositionLotsFilter {
448
+ broker_id?: string;
449
+ connection_id?: string;
450
+ account_id?: string;
451
+ symbol?: string;
452
+ position_id?: string;
453
+ limit?: number;
454
+ offset?: number;
455
+ }
456
+
457
+ export interface PositionLotFillsFilter {
458
+ connection_id?: string;
459
+ limit?: number;
460
+ offset?: number;
330
461
  }
@@ -2,6 +2,18 @@
2
2
  * Pagination-related types and classes
3
3
  */
4
4
 
5
+ import { setupLogger, buildLoggerExtra, LoggerExtra } from '../../lib/logger';
6
+
7
+ const paginationLogger = setupLogger('FinaticClientSDK.Pagination', undefined, {
8
+ codebase: 'FinaticClientSDK',
9
+ });
10
+
11
+ const buildPaginationExtra = (functionName: string, metadata?: Record<string, unknown>): LoggerExtra => ({
12
+ module: 'PaginatedResult',
13
+ function: functionName,
14
+ ...(metadata ? buildLoggerExtra(metadata) : {}),
15
+ });
16
+
5
17
  export interface ApiPaginationInfo {
6
18
  has_more: boolean;
7
19
  next_offset: number;
@@ -62,7 +74,10 @@ export class PaginatedResult<T> {
62
74
  try {
63
75
  return await this.navigationCallback(this.metadata.nextOffset, this.metadata.limit);
64
76
  } catch (error) {
65
- console.error('Error fetching next page:', error);
77
+ paginationLogger.exception('Error fetching next page', error, buildPaginationExtra('nextPage', {
78
+ next_offset: this.metadata.nextOffset,
79
+ limit: this.metadata.limit,
80
+ }));
66
81
  return null;
67
82
  }
68
83
  }
@@ -76,7 +91,10 @@ export class PaginatedResult<T> {
76
91
  try {
77
92
  return await this.navigationCallback(previousOffset, this.metadata.limit);
78
93
  } catch (error) {
79
- console.error('Error fetching previous page:', error);
94
+ paginationLogger.exception('Error fetching previous page', error, buildPaginationExtra('previousPage', {
95
+ previous_offset: previousOffset,
96
+ limit: this.metadata.limit,
97
+ }));
80
98
  return null;
81
99
  }
82
100
  }
@@ -90,7 +108,11 @@ export class PaginatedResult<T> {
90
108
  try {
91
109
  return await this.navigationCallback(offset, this.metadata.limit);
92
110
  } catch (error) {
93
- console.error('Error fetching page:', pageNumber, error);
111
+ paginationLogger.exception('Error fetching page', error, buildPaginationExtra('goToPage', {
112
+ page_number: pageNumber,
113
+ offset,
114
+ limit: this.metadata.limit,
115
+ }));
94
116
  return null;
95
117
  }
96
118
  }
@@ -103,7 +125,9 @@ export class PaginatedResult<T> {
103
125
  try {
104
126
  return await this.navigationCallback(0, this.metadata.limit);
105
127
  } catch (error) {
106
- console.error('Error fetching first page:', error);
128
+ paginationLogger.exception('Error fetching first page', error, buildPaginationExtra('firstPage', {
129
+ limit: this.metadata.limit,
130
+ }));
107
131
  return null;
108
132
  }
109
133
  }
@@ -127,7 +151,9 @@ export class PaginatedResult<T> {
127
151
  try {
128
152
  return await findLast(this);
129
153
  } catch (error) {
130
- console.error('Error fetching last page:', error);
154
+ paginationLogger.exception('Error fetching last page', error, buildPaginationExtra('lastPage', {
155
+ limit: this.metadata.limit,
156
+ }));
131
157
  return null;
132
158
  }
133
159
  }