@umituz/react-native-design-system 2.8.41 → 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 +1 -1
- package/src/infinite-scroll/presentation/hooks/pagination.helper.ts +56 -0
- package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +29 -140
- package/src/tanstack/domain/repositories/BaseRepository.ts +16 -55
- package/src/tanstack/domain/repositories/RepositoryTypes.ts +39 -0
- package/src/tanstack/index.ts +13 -13
- package/src/tanstack/infrastructure/monitoring/DevMonitor.ts +8 -90
- package/src/tanstack/infrastructure/monitoring/DevMonitor.types.ts +40 -0
- package/src/tanstack/infrastructure/monitoring/DevMonitorLogger.ts +58 -0
- package/src/timezone/domain/entities/Timezone.ts +16 -221
- package/src/timezone/domain/entities/TimezoneCapabilities.ts +189 -0
- package/src/timezone/domain/entities/TimezoneTypes.ts +47 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.8.
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
}
|
package/src/tanstack/index.ts
CHANGED
|
@@ -62,13 +62,13 @@ export {
|
|
|
62
62
|
} from './domain/utils/ErrorHelpers';
|
|
63
63
|
|
|
64
64
|
// Domain - Repositories
|
|
65
|
-
export {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} from './domain/repositories/
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
14
|
+
* Aggregates all timezone capabilities into a single service contract
|
|
63
15
|
*/
|
|
64
|
-
export interface ITimezoneService
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
+
}
|