@umituz/react-native-design-system 2.8.42 → 2.8.43

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.8.42",
3
+ "version": "2.8.43",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, and onboarding utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -6,6 +6,62 @@
6
6
  import type { InfiniteScrollConfig } from "../../domain/types/infinite-scroll-config";
7
7
  import type { InfiniteScrollState } from "../../domain/types/infinite-scroll-state";
8
8
 
9
+ /**
10
+ * Sleep utility for retry delay
11
+ */
12
+ export function sleep(ms: number): Promise<void> {
13
+ return new Promise((resolve) => setTimeout(resolve, ms));
14
+ }
15
+
16
+ /**
17
+ * Retry logic with exponential backoff
18
+ */
19
+ export async function retryWithBackoff<T>(
20
+ fn: () => Promise<T>,
21
+ maxRetries: number,
22
+ baseDelay: number,
23
+ ): Promise<T> {
24
+ let lastError: Error | undefined;
25
+
26
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
27
+ try {
28
+ return await fn();
29
+ } catch (error) {
30
+ lastError = error instanceof Error ? error : new Error(String(error));
31
+
32
+ if (attempt < maxRetries) {
33
+ const delay = baseDelay * Math.pow(2, attempt);
34
+ if (__DEV__) {
35
+ console.log(
36
+ `[useInfiniteScroll] Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`,
37
+ );
38
+ }
39
+ await sleep(delay);
40
+ }
41
+ }
42
+ }
43
+
44
+ throw lastError;
45
+ }
46
+
47
+ export function createInitialState<T>(
48
+ initialPage: number,
49
+ totalItems?: number,
50
+ ): InfiniteScrollState<T> {
51
+ return {
52
+ items: [],
53
+ pages: [],
54
+ currentPage: initialPage,
55
+ cursor: null,
56
+ hasMore: true,
57
+ isLoading: true,
58
+ isLoadingMore: false,
59
+ isRefreshing: false,
60
+ error: null,
61
+ totalItems,
62
+ };
63
+ }
64
+
9
65
  export function isCursorMode<T>(
10
66
  config: InfiniteScrollConfig<T>,
11
67
  ): config is Extract<InfiniteScrollConfig<T>, { paginationMode: "cursor" }> {
@@ -13,7 +13,13 @@ import { useState, useCallback, useEffect, useRef } from "react";
13
13
  import type { InfiniteScrollConfig } from "../../domain/types/infinite-scroll-config";
14
14
  import type { InfiniteScrollState } from "../../domain/types/infinite-scroll-state";
15
15
  import type { UseInfiniteScrollReturn } from "../../domain/types/infinite-scroll-return";
16
- import { loadData, loadMoreData, isCursorMode } from "./pagination.helper";
16
+ import {
17
+ loadData,
18
+ loadMoreData,
19
+ isCursorMode,
20
+ createInitialState,
21
+ retryWithBackoff,
22
+ } from "./pagination.helper";
17
23
 
18
24
  const DEFAULT_CONFIG = {
19
25
  pageSize: 20,
@@ -24,62 +30,6 @@ const DEFAULT_CONFIG = {
24
30
  retryDelay: 1000,
25
31
  };
26
32
 
27
- function createInitialState<T>(
28
- initialPage: number,
29
- totalItems?: number,
30
- ): InfiniteScrollState<T> {
31
- return {
32
- items: [],
33
- pages: [],
34
- currentPage: initialPage,
35
- cursor: null,
36
- hasMore: true,
37
- isLoading: true,
38
- isLoadingMore: false,
39
- isRefreshing: false,
40
- error: null,
41
- totalItems,
42
- };
43
- }
44
-
45
- /**
46
- * Sleep utility for retry delay
47
- */
48
- function sleep(ms: number): Promise<void> {
49
- return new Promise((resolve) => setTimeout(resolve, ms));
50
- }
51
-
52
- /**
53
- * Retry logic with exponential backoff
54
- */
55
- async function retryWithBackoff<T>(
56
- fn: () => Promise<T>,
57
- maxRetries: number,
58
- baseDelay: number,
59
- ): Promise<T> {
60
- let lastError: Error | undefined;
61
-
62
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
63
- try {
64
- return await fn();
65
- } catch (error) {
66
- lastError = error instanceof Error ? error : new Error(String(error));
67
-
68
- if (attempt < maxRetries) {
69
- const delay = baseDelay * Math.pow(2, attempt);
70
- if (__DEV__) {
71
- console.log(
72
- `[useInfiniteScroll] Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`,
73
- );
74
- }
75
- await sleep(delay);
76
- }
77
- }
78
- }
79
-
80
- throw lastError;
81
- }
82
-
83
33
  export function useInfiniteScroll<T>(
84
34
  config: InfiniteScrollConfig<T>,
85
35
  ): UseInfiniteScrollReturn<T> {
@@ -125,20 +75,16 @@ export function useInfiniteScroll<T>(
125
75
  const loadInitial = useCallback(async () => {
126
76
  if (isLoadingRef.current) return;
127
77
  isLoadingRef.current = true;
128
-
129
78
  cancelPendingRequests();
130
79
 
131
- if (isMountedRef.current) {
132
- setState((prev) => ({ ...prev, isLoading: true, error: null }));
133
- }
80
+ if (isMountedRef.current) setState((prev) => ({ ...prev, isLoading: true, error: null }));
134
81
 
135
82
  const startTime = __DEV__ ? performance.now() : 0;
136
83
 
137
84
  try {
138
85
  const newState = await retryWithBackoff(
139
86
  async () => {
140
- const result = await loadData(config, initialPage, pageSize, totalItems);
141
- return result;
87
+ return await loadData(config, initialPage, pageSize, totalItems);
142
88
  },
143
89
  maxRetries,
144
90
  retryDelay,
@@ -146,7 +92,6 @@ export function useInfiniteScroll<T>(
146
92
 
147
93
  if (isMountedRef.current) {
148
94
  setState(newState);
149
-
150
95
  if (__DEV__) {
151
96
  const duration = performance.now() - startTime;
152
97
  console.log(
@@ -157,18 +102,9 @@ export function useInfiniteScroll<T>(
157
102
  }
158
103
  } catch (error) {
159
104
  if (isMountedRef.current) {
160
- const errorMessage =
161
- error instanceof Error ? error.message : "Failed to load data";
162
-
163
- setState((prev) => ({
164
- ...prev,
165
- isLoading: false,
166
- error: errorMessage,
167
- }));
168
-
169
- if (__DEV__) {
170
- console.error("[useInfiniteScroll] Load initial failed:", errorMessage);
171
- }
105
+ const errorMessage = error instanceof Error ? error.message : "Failed to load data";
106
+ setState((prev) => ({ ...prev, isLoading: false, error: errorMessage }));
107
+ if (__DEV__) console.error("[useInfiniteScroll] Load initial failed:", errorMessage);
172
108
  }
173
109
  } finally {
174
110
  isLoadingRef.current = false;
@@ -181,25 +117,20 @@ export function useInfiniteScroll<T>(
181
117
  !state.hasMore ||
182
118
  state.isLoadingMore ||
183
119
  state.isLoading
184
- ) {
120
+ )
185
121
  return;
186
- }
187
122
 
188
123
  if (isCursorMode(config) && !state.cursor) return;
189
124
 
190
125
  isLoadingRef.current = true;
191
-
192
- if (isMountedRef.current) {
193
- setState((prev) => ({ ...prev, isLoadingMore: true, error: null }));
194
- }
126
+ if (isMountedRef.current) setState((prev) => ({ ...prev, isLoadingMore: true, error: null }));
195
127
 
196
128
  const startTime = __DEV__ ? performance.now() : 0;
197
129
 
198
130
  try {
199
131
  const updates = await retryWithBackoff(
200
132
  async () => {
201
- const result = await loadMoreData(config, state, pageSize);
202
- return result;
133
+ return await loadMoreData(config, state, pageSize);
203
134
  },
204
135
  maxRetries,
205
136
  retryDelay,
@@ -207,30 +138,19 @@ export function useInfiniteScroll<T>(
207
138
 
208
139
  if (isMountedRef.current) {
209
140
  setState((prev) => ({ ...prev, ...updates }));
210
-
211
141
  if (__DEV__) {
212
142
  const duration = performance.now() - startTime;
213
- const newItemsCount = updates.items?.length || 0;
214
143
  console.log(
215
144
  `[useInfiniteScroll] Load more completed in ${duration.toFixed(2)}ms`,
216
- `Loaded ${newItemsCount} items, total: ${updates.items?.length || 0}`,
145
+ `Loaded ${updates.items?.length || 0} items`,
217
146
  );
218
147
  }
219
148
  }
220
149
  } catch (error) {
221
150
  if (isMountedRef.current) {
222
- const errorMessage =
223
- error instanceof Error ? error.message : "Failed to load more items";
224
-
225
- setState((prev) => ({
226
- ...prev,
227
- isLoadingMore: false,
228
- error: errorMessage,
229
- }));
230
-
231
- if (__DEV__) {
232
- console.error("[useInfiniteScroll] Load more failed:", errorMessage);
233
- }
151
+ const errorMessage = error instanceof Error ? error.message : "Failed to load more items";
152
+ setState((prev) => ({ ...prev, isLoadingMore: false, error: errorMessage }));
153
+ if (__DEV__) console.error("[useInfiniteScroll] Load more failed:", errorMessage);
234
154
  }
235
155
  } finally {
236
156
  isLoadingRef.current = false;
@@ -240,20 +160,16 @@ export function useInfiniteScroll<T>(
240
160
  const refresh = useCallback(async () => {
241
161
  if (isLoadingRef.current) return;
242
162
  isLoadingRef.current = true;
243
-
244
163
  cancelPendingRequests();
245
164
 
246
- if (isMountedRef.current) {
247
- setState((prev) => ({ ...prev, isRefreshing: true, error: null }));
248
- }
165
+ if (isMountedRef.current) setState((prev) => ({ ...prev, isRefreshing: true, error: null }));
249
166
 
250
167
  const startTime = __DEV__ ? performance.now() : 0;
251
168
 
252
169
  try {
253
170
  const newState = await retryWithBackoff(
254
171
  async () => {
255
- const result = await loadData(config, initialPage, pageSize, totalItems);
256
- return result;
172
+ return await loadData(config, initialPage, pageSize, totalItems);
257
173
  },
258
174
  maxRetries,
259
175
  retryDelay,
@@ -261,7 +177,6 @@ export function useInfiniteScroll<T>(
261
177
 
262
178
  if (isMountedRef.current) {
263
179
  setState(newState);
264
-
265
180
  if (__DEV__) {
266
181
  const duration = performance.now() - startTime;
267
182
  console.log(
@@ -272,18 +187,9 @@ export function useInfiniteScroll<T>(
272
187
  }
273
188
  } catch (error) {
274
189
  if (isMountedRef.current) {
275
- const errorMessage =
276
- error instanceof Error ? error.message : "Failed to refresh data";
277
-
278
- setState((prev) => ({
279
- ...prev,
280
- isRefreshing: false,
281
- error: errorMessage,
282
- }));
283
-
284
- if (__DEV__) {
285
- console.error("[useInfiniteScroll] Refresh failed:", errorMessage);
286
- }
190
+ const errorMessage = error instanceof Error ? error.message : "Failed to refresh data";
191
+ setState((prev) => ({ ...prev, isRefreshing: false, error: errorMessage }));
192
+ if (__DEV__) console.error("[useInfiniteScroll] Refresh failed:", errorMessage);
287
193
  }
288
194
  } finally {
289
195
  isLoadingRef.current = false;
@@ -294,34 +200,17 @@ export function useInfiniteScroll<T>(
294
200
  isLoadingRef.current = false;
295
201
  cancelPendingRequests();
296
202
  setState(createInitialState<T>(initialPage, totalItems));
297
-
298
- if (__DEV__) {
299
- console.log("[useInfiniteScroll] State reset");
300
- }
203
+ if (__DEV__) console.log("[useInfiniteScroll] State reset");
301
204
  }, [initialPage, totalItems, cancelPendingRequests]);
302
205
 
303
206
  useEffect(() => {
304
- if (autoLoad) {
305
- loadInitial();
306
- }
307
-
207
+ if (autoLoad) loadInitial();
308
208
  return () => {
309
- // Cleanup on config change
310
- if (__DEV__) {
311
- console.log("[useInfiniteScroll] Config changed, cleaning up");
312
- }
209
+ if (__DEV__) console.log("[useInfiniteScroll] Config changed, cleaning up");
313
210
  };
314
211
  }, [autoLoad, loadInitial]);
315
212
 
316
- const canLoadMore =
317
- state.hasMore && !state.isLoadingMore && !state.isLoading;
213
+ const canLoadMore = state.hasMore && !state.isLoadingMore && !state.isLoading;
318
214
 
319
- return {
320
- items: state.items,
321
- state,
322
- loadMore,
323
- refresh,
324
- reset,
325
- canLoadMore,
326
- };
215
+ return { items: state.items, state, loadMore, refresh, reset, canLoadMore };
327
216
  }
@@ -35,43 +35,16 @@
35
35
  * ```
36
36
  */
37
37
 
38
- import type { QueryClient } from '@tanstack/react-query';
38
+ import type { QueryClient, QueryKey } from '@tanstack/react-query';
39
39
  import { getGlobalQueryClient } from '../../infrastructure/config/QueryClientSingleton';
40
- import type { CacheConfig } from '../types/CacheStrategy';
41
40
  import { CacheStrategies } from '../../infrastructure/config/QueryClientConfig';
42
-
43
- export interface CreateParams<TVariables> {
44
- variables: TVariables;
45
- }
46
-
47
- export interface UpdateParams<TVariables> {
48
- id: string | number;
49
- variables: TVariables;
50
- }
51
-
52
- export interface ListParams {
53
- page?: number;
54
- limit?: number;
55
- filter?: Record<string, unknown>;
56
- }
57
-
58
- export interface RepositoryOptions {
59
- /**
60
- * Cache strategy for queries
61
- * @default CacheStrategies.PUBLIC_DATA
62
- */
63
- cacheStrategy?: CacheConfig;
64
-
65
- /**
66
- * Stale time override
67
- */
68
- staleTime?: number;
69
-
70
- /**
71
- * GC time override
72
- */
73
- gcTime?: number;
74
- }
41
+ import { createQueryKeyFactory } from '../utils/QueryKeyFactory';
42
+ import type {
43
+ CreateParams,
44
+ UpdateParams,
45
+ ListParams,
46
+ RepositoryOptions,
47
+ } from './RepositoryTypes';
75
48
 
76
49
  /**
77
50
  * Base repository for CRUD operations
@@ -91,13 +64,7 @@ export abstract class BaseRepository<
91
64
  /**
92
65
  * Query key factory for this repository
93
66
  */
94
- public readonly keys: {
95
- all: () => readonly [string];
96
- lists: () => readonly [string, 'list'];
97
- list: (params: ListParams) => readonly [string, 'list', ListParams];
98
- details: () => readonly [string, 'detail'];
99
- detail: (id: string | number) => readonly [string, 'detail', string | number];
100
- };
67
+ public readonly keys: ReturnType<typeof createQueryKeyFactory>;
101
68
 
102
69
  constructor(resource: string, options: RepositoryOptions = {}) {
103
70
  this.resource = resource;
@@ -106,13 +73,7 @@ export abstract class BaseRepository<
106
73
  ...options,
107
74
  };
108
75
 
109
- this.keys = {
110
- all: () => [this.resource] as const,
111
- lists: () => [this.resource, 'list'] as const,
112
- list: (params: ListParams) => [this.resource, 'list', params] as const,
113
- details: () => [this.resource, 'detail'] as const,
114
- detail: (id: string | number) => [this.resource, 'detail', id] as const,
115
- };
76
+ this.keys = createQueryKeyFactory(this.resource);
116
77
  }
117
78
 
118
79
  /**
@@ -162,11 +123,11 @@ export abstract class BaseRepository<
162
123
  */
163
124
  async queryAll(params?: ListParams): Promise<TData[]> {
164
125
  const client = this.getClient();
165
- const queryKey = params ? this.keys.list(params) : this.keys.lists();
126
+ const queryKey = params ? this.keys.list(params as Record<string, unknown>) : this.keys.lists();
166
127
  const cacheOptions = this.getCacheOptions();
167
128
 
168
129
  return client.fetchQuery({
169
- queryKey,
130
+ queryKey: queryKey as QueryKey,
170
131
  queryFn: () => this.fetchAll(params),
171
132
  ...cacheOptions,
172
133
  });
@@ -182,7 +143,7 @@ export abstract class BaseRepository<
182
143
 
183
144
  try {
184
145
  return client.fetchQuery({
185
- queryKey,
146
+ queryKey: queryKey as QueryKey,
186
147
  queryFn: () => this.fetchById(id),
187
148
  ...cacheOptions,
188
149
  });
@@ -196,11 +157,11 @@ export abstract class BaseRepository<
196
157
  */
197
158
  async prefetchAll(params?: ListParams): Promise<void> {
198
159
  const client = this.getClient();
199
- const queryKey = params ? this.keys.list(params) : this.keys.lists();
160
+ const queryKey = params ? this.keys.list(params as Record<string, unknown>) : this.keys.lists();
200
161
  const cacheOptions = this.getCacheOptions();
201
162
 
202
163
  await client.prefetchQuery({
203
- queryKey,
164
+ queryKey: queryKey as QueryKey,
204
165
  queryFn: () => this.fetchAll(params),
205
166
  ...cacheOptions,
206
167
  });
@@ -215,7 +176,7 @@ export abstract class BaseRepository<
215
176
  const cacheOptions = this.getCacheOptions();
216
177
 
217
178
  await client.prefetchQuery({
218
- queryKey,
179
+ queryKey: queryKey as QueryKey,
219
180
  queryFn: () => this.fetchById(id),
220
181
  ...cacheOptions,
221
182
  });
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Repository Types
3
+ * Interfaces for data repository operations
4
+ */
5
+
6
+ import type { CacheConfig } from '../types/CacheStrategy';
7
+
8
+ export interface CreateParams<TVariables> {
9
+ variables: TVariables;
10
+ }
11
+
12
+ export interface UpdateParams<TVariables> {
13
+ id: string | number;
14
+ variables: TVariables;
15
+ }
16
+
17
+ export interface ListParams {
18
+ page?: number;
19
+ limit?: number;
20
+ filter?: Record<string, unknown>;
21
+ }
22
+
23
+ export interface RepositoryOptions {
24
+ /**
25
+ * Cache strategy for queries
26
+ * @default CacheStrategies.PUBLIC_DATA
27
+ */
28
+ cacheStrategy?: CacheConfig;
29
+
30
+ /**
31
+ * Stale time override
32
+ */
33
+ staleTime?: number;
34
+
35
+ /**
36
+ * GC time override
37
+ */
38
+ gcTime?: number;
39
+ }
@@ -62,13 +62,13 @@ export {
62
62
  } from './domain/utils/ErrorHelpers';
63
63
 
64
64
  // Domain - Repositories
65
- export {
66
- BaseRepository,
67
- type CreateParams,
68
- type UpdateParams,
69
- type ListParams,
70
- type RepositoryOptions,
71
- } from './domain/repositories/BaseRepository';
65
+ export { BaseRepository } from './domain/repositories/BaseRepository';
66
+ export type {
67
+ CreateParams,
68
+ UpdateParams,
69
+ ListParams,
70
+ RepositoryOptions,
71
+ } from './domain/repositories/RepositoryTypes';
72
72
 
73
73
  export { RepositoryFactory } from './domain/repositories/RepositoryFactory';
74
74
 
@@ -95,12 +95,12 @@ export {
95
95
  } from './infrastructure/config/QueryClientSingleton';
96
96
 
97
97
  // Infrastructure - Monitoring
98
- export {
99
- DevMonitor,
100
- type QueryMetrics,
101
- type CacheStats as QueryCacheStats,
102
- type DevMonitorOptions,
103
- } from './infrastructure/monitoring/DevMonitor';
98
+ export { DevMonitor } from './infrastructure/monitoring/DevMonitor';
99
+ export type {
100
+ QueryMetrics,
101
+ CacheStats as QueryCacheStats,
102
+ DevMonitorOptions,
103
+ } from './infrastructure/monitoring/DevMonitor.types';
104
104
 
105
105
  // Infrastructure - Providers
106
106
  export { TanstackProvider, type TanstackProviderProps } from './infrastructure/providers/TanstackProvider';
@@ -7,43 +7,8 @@
7
7
  */
8
8
 
9
9
  import type { Query, QueryClient } from '@tanstack/react-query';
10
-
11
- export interface QueryMetrics {
12
- queryKey: readonly unknown[];
13
- fetchCount: number;
14
- totalFetchTime: number;
15
- averageFetchTime: number;
16
- slowFetchCount: number;
17
- lastFetchTime: number | null;
18
- }
19
-
20
- export interface CacheStats {
21
- totalQueries: number;
22
- activeQueries: number;
23
- cachedQueries: number;
24
- staleQueries: number;
25
- inactiveQueries: number;
26
- }
27
-
28
- export interface DevMonitorOptions {
29
- /**
30
- * Threshold for slow query detection (in ms)
31
- * @default 1000
32
- */
33
- slowQueryThreshold?: number;
34
-
35
- /**
36
- * Enable console logging
37
- * @default true
38
- */
39
- enableLogging?: boolean;
40
-
41
- /**
42
- * Log interval for stats (in ms)
43
- * @default 30000 (30 seconds)
44
- */
45
- statsLogInterval?: number;
46
- }
10
+ import type { QueryMetrics, CacheStats, DevMonitorOptions } from './DevMonitor.types';
11
+ import { DevMonitorLogger } from './DevMonitorLogger';
47
12
 
48
13
  class DevMonitorClass {
49
14
  private metrics: Map<string, QueryMetrics> = new Map();
@@ -67,12 +32,9 @@ class DevMonitorClass {
67
32
 
68
33
  private init(): void {
69
34
  if (!this.isEnabled) return;
70
-
71
35
  if (this.options.enableLogging) {
72
-
73
- console.log('[TanStack DevMonitor] Monitoring initialized');
36
+ DevMonitorLogger.logInit();
74
37
  }
75
-
76
38
  this.startStatsLogging();
77
39
  }
78
40
 
@@ -106,12 +68,8 @@ class DevMonitorClass {
106
68
 
107
69
  if (fetchTime > this.options.slowQueryThreshold) {
108
70
  metrics.slowFetchCount++;
109
-
110
71
  if (this.options.enableLogging) {
111
-
112
- console.warn(
113
- `[TanStack DevMonitor] Slow query detected: ${queryKeyString} (${fetchTime}ms)`,
114
- );
72
+ DevMonitorLogger.logSlowQuery(queryKeyString, fetchTime);
115
73
  }
116
74
  }
117
75
  }
@@ -123,14 +81,12 @@ class DevMonitorClass {
123
81
  if (!this.isEnabled) return;
124
82
 
125
83
  this.queryClient = queryClient;
126
-
127
84
  queryClient.getQueryCache().subscribe((query) => {
128
85
  this.trackQuery(query as unknown as Query);
129
86
  });
130
87
 
131
88
  if (this.options.enableLogging) {
132
-
133
- console.log('[TanStack DevMonitor] Attached to QueryClient');
89
+ DevMonitorLogger.logAttached();
134
90
  }
135
91
  }
136
92
 
@@ -182,40 +138,7 @@ class DevMonitorClass {
182
138
  */
183
139
  logReport(): void {
184
140
  if (!this.isEnabled || !this.options.enableLogging) return;
185
-
186
- const stats = this.getCacheStats();
187
- const slowQueries = this.getSlowQueries();
188
-
189
-
190
- console.group('[TanStack DevMonitor] Performance Report');
191
-
192
- if (stats) {
193
-
194
- console.table({
195
- 'Total Queries': stats.totalQueries,
196
- 'Active Queries': stats.activeQueries,
197
- 'Cached Queries': stats.cachedQueries,
198
- 'Stale Queries': stats.staleQueries,
199
- 'Inactive Queries': stats.inactiveQueries,
200
- });
201
- }
202
-
203
- if (slowQueries.length > 0) {
204
-
205
- console.warn(`Found ${slowQueries.length} slow queries:`);
206
-
207
- console.table(
208
- slowQueries.map((m) => ({
209
- queryKey: JSON.stringify(m.queryKey),
210
- fetchCount: m.fetchCount,
211
- avgTime: `${m.averageFetchTime.toFixed(2)}ms`,
212
- slowCount: m.slowFetchCount,
213
- })),
214
- );
215
- }
216
-
217
-
218
- console.groupEnd();
141
+ DevMonitorLogger.logReport(this.getCacheStats(), this.getSlowQueries());
219
142
  }
220
143
 
221
144
  /**
@@ -223,7 +146,6 @@ class DevMonitorClass {
223
146
  */
224
147
  private startStatsLogging(): void {
225
148
  if (!this.isEnabled || this.statsInterval !== null) return;
226
-
227
149
  this.statsInterval = setInterval(() => {
228
150
  this.logReport();
229
151
  }, this.options.statsLogInterval);
@@ -245,10 +167,8 @@ class DevMonitorClass {
245
167
  clear(): void {
246
168
  if (!this.isEnabled) return;
247
169
  this.metrics.clear();
248
-
249
170
  if (this.options.enableLogging) {
250
-
251
- console.log('[TanStack DevMonitor] Metrics cleared');
171
+ DevMonitorLogger.logMethodsCleared();
252
172
  }
253
173
  }
254
174
 
@@ -260,10 +180,8 @@ class DevMonitorClass {
260
180
  this.stopStatsLogging();
261
181
  this.clear();
262
182
  this.queryClient = null;
263
-
264
183
  if (this.options.enableLogging) {
265
-
266
- console.log('[TanStack DevMonitor] Reset');
184
+ DevMonitorLogger.logReset();
267
185
  }
268
186
  }
269
187
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * DevMonitor Types
3
+ */
4
+
5
+ export interface QueryMetrics {
6
+ queryKey: readonly unknown[];
7
+ fetchCount: number;
8
+ totalFetchTime: number;
9
+ averageFetchTime: number;
10
+ slowFetchCount: number;
11
+ lastFetchTime: number | null;
12
+ }
13
+
14
+ export interface CacheStats {
15
+ totalQueries: number;
16
+ activeQueries: number;
17
+ cachedQueries: number;
18
+ staleQueries: number;
19
+ inactiveQueries: number;
20
+ }
21
+
22
+ export interface DevMonitorOptions {
23
+ /**
24
+ * Threshold for slow query detection (in ms)
25
+ * @default 1000
26
+ */
27
+ slowQueryThreshold?: number;
28
+
29
+ /**
30
+ * Enable console logging
31
+ * @default true
32
+ */
33
+ enableLogging?: boolean;
34
+
35
+ /**
36
+ * Log interval for stats (in ms)
37
+ * @default 30000 (30 seconds)
38
+ */
39
+ statsLogInterval?: number;
40
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * DevMonitor Logger
3
+ * Helper for logging performance reports
4
+ */
5
+
6
+ import type { QueryMetrics, CacheStats } from './DevMonitor.types';
7
+
8
+ export class DevMonitorLogger {
9
+ static logInit(): void {
10
+ console.log('[TanStack DevMonitor] Monitoring initialized');
11
+ }
12
+
13
+ static logSlowQuery(queryKeyString: string, fetchTime: number): void {
14
+ console.warn(
15
+ `[TanStack DevMonitor] Slow query detected: ${queryKeyString} (${fetchTime}ms)`,
16
+ );
17
+ }
18
+
19
+ static logAttached(): void {
20
+ console.log('[TanStack DevMonitor] Attached to QueryClient');
21
+ }
22
+
23
+ static logMethodsCleared(): void {
24
+ console.log('[TanStack DevMonitor] Metrics cleared');
25
+ }
26
+
27
+ static logReset(): void {
28
+ console.log('[TanStack DevMonitor] Reset');
29
+ }
30
+
31
+ static logReport(stats: CacheStats | null, slowQueries: QueryMetrics[]): void {
32
+ console.group('[TanStack DevMonitor] Performance Report');
33
+
34
+ if (stats) {
35
+ console.table({
36
+ 'Total Queries': stats.totalQueries,
37
+ 'Active Queries': stats.activeQueries,
38
+ 'Cached Queries': stats.cachedQueries,
39
+ 'Stale Queries': stats.staleQueries,
40
+ 'Inactive Queries': stats.inactiveQueries,
41
+ });
42
+ }
43
+
44
+ if (slowQueries.length > 0) {
45
+ console.warn(`Found ${slowQueries.length} slow queries:`);
46
+ console.table(
47
+ slowQueries.map((m) => ({
48
+ queryKey: JSON.stringify(m.queryKey),
49
+ fetchCount: m.fetchCount,
50
+ avgTime: `${m.averageFetchTime.toFixed(2)}ms`,
51
+ slowCount: m.slowFetchCount,
52
+ })),
53
+ );
54
+ }
55
+
56
+ console.groupEnd();
57
+ }
58
+ }
@@ -1,227 +1,22 @@
1
- /**
2
- * Timezone Domain Entities
3
- *
4
- * Core timezone types and interfaces for device timezone detection
5
- * and date/time manipulation
6
- *
7
- * Features:
8
- * - Auto-detects device timezone
9
- * - Locale-aware date/time formatting
10
- * - Calendar utilities
11
- * - Date manipulation helpers
12
- *
13
- * Zero external dependencies - uses native Intl API
14
- */
15
-
16
- /**
17
- * Timezone information from device
18
- */
19
- export interface TimezoneInfo {
20
- /** IANA timezone identifier (e.g., "America/New_York", "Europe/Istanbul") */
21
- timezone: string;
22
-
23
- /** UTC offset in minutes (negative for west, positive for east) */
24
- offset: number;
25
-
26
- /** Human-readable timezone display name */
27
- displayName: string;
28
- }
29
-
30
- /**
31
- * Calendar day representation
32
- * Generic structure - can be extended by apps for app-specific data
33
- */
34
- export interface TimezoneCalendarDay {
35
- /** Date object for this day */
36
- date: Date;
37
-
38
- /** Day of month (1-31) */
39
- day: number;
40
-
41
- /** Day of week (0 = Sunday, 6 = Saturday) */
42
- dayOfWeek: number;
43
-
44
- /** Month (0-11, 0 = January) */
45
- month: number;
46
-
47
- /** Year (e.g., 2024) */
48
- year: number;
49
-
50
- /** Whether this day is in the current month */
51
- isCurrentMonth: boolean;
1
+ import type { TimezoneInfo, TimezoneCalendarDay } from './TimezoneTypes';
2
+ import type {
3
+ ITimezoneCore,
4
+ ITimezoneFormatting,
5
+ ITimezoneManipulation,
6
+ ITimezoneComparison,
7
+ ITimezoneCalendar,
8
+ } from './TimezoneCapabilities';
52
9
 
53
- /** Whether this day is today */
54
- isToday: boolean;
55
-
56
- /** ISO date string (YYYY-MM-DD) */
57
- isoDate: string;
58
- }
10
+ export type { TimezoneInfo, TimezoneCalendarDay };
59
11
 
60
12
  /**
61
13
  * Timezone Service Interface
62
- * Defines contract for timezone operations
14
+ * Aggregates all timezone capabilities into a single service contract
63
15
  */
64
- export interface ITimezoneService {
65
- /** Get current device timezone (IANA identifier) */
66
- getCurrentTimezone(): string;
67
-
68
- /** Get timezone offset in minutes */
69
- getTimezoneOffset(): number;
70
-
71
- /** Get complete timezone information */
72
- getTimezoneInfo(): TimezoneInfo;
73
-
74
- /** Format date with locale support */
75
- formatDate(
76
- date: Date | string | number,
77
- locale?: string,
78
- options?: Intl.DateTimeFormatOptions,
79
- ): string;
80
-
81
- /** Format time with locale support */
82
- formatTime(
83
- date: Date | string | number,
84
- locale?: string,
85
- options?: Intl.DateTimeFormatOptions,
86
- ): string;
87
-
88
- /** Get calendar days for a month */
89
- getCalendarDays(year: number, month: number): TimezoneCalendarDay[];
90
-
91
- /** Check if date is today */
92
- isToday(date: Date | string | number): boolean;
93
-
94
- /** Check if two dates are the same day */
95
- isSameDay(date1: Date | string | number, date2: Date | string | number): boolean;
96
-
97
- /** Add days to a date */
98
- addDays(date: Date | string | number, days: number): Date;
99
-
100
- /** Get start of day (00:00:00) */
101
- startOfDay(date: Date | string | number): Date;
102
-
103
- /** Get end of day (23:59:59.999) */
104
- endOfDay(date: Date | string | number): Date;
105
-
106
- /** Format date to ISO string (YYYY-MM-DD) */
107
- formatDateToString(date: Date | string | number): string;
108
-
109
- /** Format to display date string (DD.MM.YYYY) */
110
- formatToDisplayDate(date: Date | string | number): string;
111
-
112
- /** Format to display date time string (DD.MM.YYYY HH:mm) */
113
- formatToDisplayDateTime(date: Date | string | number): string;
114
-
115
- /** Get current date as ISO string (YYYY-MM-DDTHH:mm:ss.sssZ) */
116
- getCurrentISOString(): string;
117
-
118
- /** Get current date object */
119
- getNow(): Date;
120
-
121
- /** Parse input to Date object */
122
- parse(date: Date | string | number): Date;
123
-
124
- /** Check if date is in the future */
125
- isFuture(date: Date | string | number): boolean;
126
-
127
- /** Check if date is in the past */
128
- isPast(date: Date | string | number): boolean;
129
-
130
- /** Get days until a future date (returns 0 if past) */
131
- getDaysUntil(date: Date | string | number): number;
132
-
133
- /** Get difference in days between two dates */
134
- getDifferenceInDays(date1: Date | string | number, date2: Date | string | number): number;
135
-
136
- /** Format date to ISO datetime string (YYYY-MM-DDTHH:mm:ss.sssZ) */
137
- formatToISOString(date: Date | string | number): string;
138
-
139
- /** Get list of common timezones for selection */
140
- getTimezones(): TimezoneInfo[];
141
-
142
- /** Check if date is valid */
143
- isValid(date: Date | string | number): boolean;
144
-
145
- /** Calculate age from birth date */
146
- getAge(birthDate: Date | string | number): number;
147
-
148
- /** Check if date is between two dates (inclusive) */
149
- isBetween(
150
- date: Date | string | number,
151
- start: Date | string | number,
152
- end: Date | string | number,
153
- ): boolean;
154
-
155
- /** Get earliest date from array */
156
- min(dates: Array<Date | string | number>): Date;
157
-
158
- /** Get latest date from array */
159
- max(dates: Array<Date | string | number>): Date;
160
-
161
- /** Get ISO week number (1-53) */
162
- getWeek(date: Date | string | number): number;
163
-
164
- /** Get quarter (1-4) */
165
- getQuarter(date: Date | string | number): number;
166
-
167
- /** Get timezone offset for specific timezone in minutes */
168
- getTimezoneOffsetFor(timezone: string, date?: Date | string | number): number;
169
-
170
- /** Convert date from one timezone to another */
171
- convertTimezone(
172
- date: Date | string | number,
173
- fromTimezone: string,
174
- toTimezone: string,
175
- ): Date;
176
-
177
- /** Format duration in milliseconds to human readable string */
178
- formatDuration(milliseconds: number): string;
179
-
180
- /** Check if date is on weekend */
181
- isWeekend(date: Date | string | number): boolean;
182
-
183
- /** Add business days (skip weekends) */
184
- addBusinessDays(date: Date | string | number, days: number): Date;
185
-
186
- /** Check if date is first day of month */
187
- isFirstDayOfMonth(date: Date | string | number): boolean;
188
-
189
- /** Check if date is last day of month */
190
- isLastDayOfMonth(date: Date | string | number): boolean;
191
-
192
- /** Get number of days in month */
193
- getDaysInMonth(date: Date | string | number): number;
194
-
195
- /** Get array of dates in range */
196
- getDateRange(
197
- start: Date | string | number,
198
- end: Date | string | number,
199
- ): Date[];
200
-
201
- /** Check if two date ranges overlap */
202
- areRangesOverlapping(
203
- start1: Date | string | number,
204
- end1: Date | string | number,
205
- start2: Date | string | number,
206
- end2: Date | string | number,
207
- ): boolean;
208
-
209
- /** Clamp date to range */
210
- clampDate(
211
- date: Date | string | number,
212
- min: Date | string | number,
213
- max: Date | string | number,
214
- ): Date;
215
-
216
- /** Check if two dates are same hour */
217
- areSameHour(date1: Date | string | number, date2: Date | string | number): boolean;
218
-
219
- /** Check if two dates are same minute */
220
- areSameMinute(date1: Date | string | number, date2: Date | string | number): boolean;
221
-
222
- /** Get middle of day (12:00:00) */
223
- getMiddleOfDay(date: Date | string | number): Date;
16
+ export interface ITimezoneService
17
+ extends ITimezoneCore,
18
+ ITimezoneFormatting,
19
+ ITimezoneManipulation,
20
+ ITimezoneComparison,
21
+ ITimezoneCalendar {}
224
22
 
225
- /** Get relative time from now ("5 minutes ago", "in 2 hours") */
226
- fromNow(date: Date | string | number, locale?: string): string;
227
- }
@@ -0,0 +1,189 @@
1
+ import type { TimezoneInfo, TimezoneCalendarDay } from './TimezoneTypes';
2
+
3
+ /**
4
+ * Core timezone detection and information checks
5
+ */
6
+ export interface ITimezoneCore {
7
+ /** Get current device timezone (IANA identifier) */
8
+ getCurrentTimezone(): string;
9
+
10
+ /** Get timezone offset in minutes */
11
+ getTimezoneOffset(): number;
12
+
13
+ /** Get complete timezone information */
14
+ getTimezoneInfo(): TimezoneInfo;
15
+
16
+ /** Get current date as ISO string (YYYY-MM-DDTHH:mm:ss.sssZ) */
17
+ getCurrentISOString(): string;
18
+
19
+ /** Get current date object */
20
+ getNow(): Date;
21
+
22
+ /** Get list of common timezones for selection */
23
+ getTimezones(): TimezoneInfo[];
24
+
25
+ /** Get timezone offset for specific timezone in minutes */
26
+ getTimezoneOffsetFor(timezone: string, date?: Date | string | number): number;
27
+ }
28
+
29
+ /**
30
+ * Date and time formatting capabilities
31
+ */
32
+ export interface ITimezoneFormatting {
33
+ /** Format date with locale support */
34
+ formatDate(
35
+ date: Date | string | number,
36
+ locale?: string,
37
+ options?: Intl.DateTimeFormatOptions,
38
+ ): string;
39
+
40
+ /** Format time with locale support */
41
+ formatTime(
42
+ date: Date | string | number,
43
+ locale?: string,
44
+ options?: Intl.DateTimeFormatOptions,
45
+ ): string;
46
+
47
+ /** Format date to ISO string (YYYY-MM-DD) */
48
+ formatDateToString(date: Date | string | number): string;
49
+
50
+ /** Format to display date string (DD.MM.YYYY) */
51
+ formatToDisplayDate(date: Date | string | number): string;
52
+
53
+ /** Format to display date time string (DD.MM.YYYY HH:mm) */
54
+ formatToDisplayDateTime(date: Date | string | number): string;
55
+
56
+ /** Format date to ISO datetime string (YYYY-MM-DDTHH:mm:ss.sssZ) */
57
+ formatToISOString(date: Date | string | number): string;
58
+
59
+ /** Format duration in milliseconds to human readable string */
60
+ formatDuration(milliseconds: number): string;
61
+
62
+ /** Get relative time from now ("5 minutes ago", "in 2 hours") */
63
+ fromNow(date: Date | string | number, locale?: string): string;
64
+ }
65
+
66
+ /**
67
+ * Date manipulation and calculation capabilities
68
+ */
69
+ export interface ITimezoneManipulation {
70
+ /** Parse input to Date object */
71
+ parse(date: Date | string | number): Date;
72
+
73
+ /** Add days to a date */
74
+ addDays(date: Date | string | number, days: number): Date;
75
+
76
+ /** Get start of day (00:00:00) */
77
+ startOfDay(date: Date | string | number): Date;
78
+
79
+ /** Get end of day (23:59:59.999) */
80
+ endOfDay(date: Date | string | number): Date;
81
+
82
+ /** Get middle of day (12:00:00) */
83
+ getMiddleOfDay(date: Date | string | number): Date;
84
+
85
+ /** Convert date from one timezone to another */
86
+ convertTimezone(
87
+ date: Date | string | number,
88
+ fromTimezone: string,
89
+ toTimezone: string,
90
+ ): Date;
91
+
92
+ /** Add business days (skip weekends) */
93
+ addBusinessDays(date: Date | string | number, days: number): Date;
94
+
95
+ /** Clamp date to range */
96
+ clampDate(
97
+ date: Date | string | number,
98
+ min: Date | string | number,
99
+ max: Date | string | number,
100
+ ): Date;
101
+ }
102
+
103
+ /**
104
+ * Date checking and comparison capabilities
105
+ */
106
+ export interface ITimezoneComparison {
107
+ /** Check if date is today */
108
+ isToday(date: Date | string | number): boolean;
109
+
110
+ /** Check if two dates are the same day */
111
+ isSameDay(date1: Date | string | number, date2: Date | string | number): boolean;
112
+
113
+ /** Check if date is in the future */
114
+ isFuture(date: Date | string | number): boolean;
115
+
116
+ /** Check if date is in the past */
117
+ isPast(date: Date | string | number): boolean;
118
+
119
+ /** Check if date is valid */
120
+ isValid(date: Date | string | number): boolean;
121
+
122
+ /** Check if date is between two dates (inclusive) */
123
+ isBetween(
124
+ date: Date | string | number,
125
+ start: Date | string | number,
126
+ end: Date | string | number,
127
+ ): boolean;
128
+
129
+ /** Check if date is on weekend */
130
+ isWeekend(date: Date | string | number): boolean;
131
+
132
+ /** Check if date is first day of month */
133
+ isFirstDayOfMonth(date: Date | string | number): boolean;
134
+
135
+ /** Check if date is last day of month */
136
+ isLastDayOfMonth(date: Date | string | number): boolean;
137
+
138
+ /** Check if two dates are same hour */
139
+ areSameHour(date1: Date | string | number, date2: Date | string | number): boolean;
140
+
141
+ /** Check if two dates are same minute */
142
+ areSameMinute(date1: Date | string | number, date2: Date | string | number): boolean;
143
+
144
+ /** Check if two date ranges overlap */
145
+ areRangesOverlapping(
146
+ start1: Date | string | number,
147
+ end1: Date | string | number,
148
+ start2: Date | string | number,
149
+ end2: Date | string | number,
150
+ ): boolean;
151
+ }
152
+
153
+ /**
154
+ * Advanced calendar and range capabilities
155
+ */
156
+ export interface ITimezoneCalendar {
157
+ /** Get calendar days for a month */
158
+ getCalendarDays(year: number, month: number): TimezoneCalendarDay[];
159
+
160
+ /** Get days until a future date (returns 0 if past) */
161
+ getDaysUntil(date: Date | string | number): number;
162
+
163
+ /** Get difference in days between two dates */
164
+ getDifferenceInDays(date1: Date | string | number, date2: Date | string | number): number;
165
+
166
+ /** Calculate age from birth date */
167
+ getAge(birthDate: Date | string | number): number;
168
+
169
+ /** Get earliest date from array */
170
+ min(dates: Array<Date | string | number>): Date;
171
+
172
+ /** Get latest date from array */
173
+ max(dates: Array<Date | string | number>): Date;
174
+
175
+ /** Get ISO week number (1-53) */
176
+ getWeek(date: Date | string | number): number;
177
+
178
+ /** Get quarter (1-4) */
179
+ getQuarter(date: Date | string | number): number;
180
+
181
+ /** Get number of days in month */
182
+ getDaysInMonth(date: Date | string | number): number;
183
+
184
+ /** Get array of dates in range */
185
+ getDateRange(
186
+ start: Date | string | number,
187
+ end: Date | string | number,
188
+ ): Date[];
189
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Timezone Types
3
+ */
4
+
5
+ /**
6
+ * Timezone information from device
7
+ */
8
+ export interface TimezoneInfo {
9
+ /** IANA timezone identifier (e.g., "America/New_York", "Europe/Istanbul") */
10
+ timezone: string;
11
+
12
+ /** UTC offset in minutes (negative for west, positive for east) */
13
+ offset: number;
14
+
15
+ /** Human-readable timezone display name */
16
+ displayName: string;
17
+ }
18
+
19
+ /**
20
+ * Calendar day representation
21
+ * Generic structure - can be extended by apps for app-specific data
22
+ */
23
+ export interface TimezoneCalendarDay {
24
+ /** Date object for this day */
25
+ date: Date;
26
+
27
+ /** Day of month (1-31) */
28
+ day: number;
29
+
30
+ /** Day of week (0 = Sunday, 6 = Saturday) */
31
+ dayOfWeek: number;
32
+
33
+ /** Month (0-11, 0 = January) */
34
+ month: number;
35
+
36
+ /** Year (e.g., 2024) */
37
+ year: number;
38
+
39
+ /** Whether this day is in the current month */
40
+ isCurrentMonth: boolean;
41
+
42
+ /** Whether this day is today */
43
+ isToday: boolean;
44
+
45
+ /** ISO date string (YYYY-MM-DD) */
46
+ isoDate: string;
47
+ }