viewlogic 1.2.2 → 1.2.4

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.
@@ -9,13 +9,8 @@ var I18nManager = class {
9
9
  constructor(router, options = {}) {
10
10
  this.config = {
11
11
  enabled: options.useI18n !== void 0 ? options.useI18n : true,
12
- defaultLanguage: options.defaultLanguage || "ko",
13
- fallbackLanguage: options.defaultLanguage || "ko",
14
- cacheKey: options.cacheKey || "viewlogic_lang",
15
- dataCacheKey: options.dataCacheKey || "viewlogic_i18n_data",
16
- cacheVersion: options.cacheVersion || "1.0.0",
17
- enableDataCache: options.enableDataCache !== false,
18
- debug: options.debug || false
12
+ defaultLanguage: options.defaultLanguage || "en",
13
+ fallbackLanguage: options.defaultLanguage || "en"
19
14
  };
20
15
  this.router = router;
21
16
  this.messages = /* @__PURE__ */ new Map();
@@ -33,10 +28,6 @@ var I18nManager = class {
33
28
  return;
34
29
  }
35
30
  this.loadLanguageFromCache();
36
- if (this.config.debug) {
37
- this.config.enableDataCache = false;
38
- this.log("debug", "Data cache disabled in debug mode");
39
- }
40
31
  if (!this.messages.has(this.currentLanguage)) {
41
32
  try {
42
33
  await this.loadMessages(this.currentLanguage);
@@ -54,7 +45,7 @@ var I18nManager = class {
54
45
  */
55
46
  loadLanguageFromCache() {
56
47
  try {
57
- const cachedLang = localStorage.getItem(this.config.cacheKey);
48
+ const cachedLang = this.router.cacheManager?.get("viewlogic_lang");
58
49
  if (cachedLang && this.isValidLanguage(cachedLang)) {
59
50
  this.currentLanguage = cachedLang;
60
51
  this.log("debug", "Language loaded from cache:", cachedLang);
@@ -118,7 +109,7 @@ var I18nManager = class {
118
109
  */
119
110
  saveLanguageToCache(language) {
120
111
  try {
121
- localStorage.setItem(this.config.cacheKey, language);
112
+ this.router.cacheManager?.set("viewlogic_lang", language);
122
113
  this.log("debug", "Language saved to cache:", language);
123
114
  } catch (error) {
124
115
  this.log("warn", "Failed to save language to cache:", error);
@@ -156,12 +147,11 @@ var I18nManager = class {
156
147
  * 파일에서 메시지 로드 (캐싱 지원)
157
148
  */
158
149
  async _loadMessagesFromFile(language) {
159
- if (this.config.enableDataCache) {
160
- const cachedData = this.getDataFromCache(language);
161
- if (cachedData) {
162
- this.log("debug", "Messages loaded from cache:", language);
163
- return cachedData;
164
- }
150
+ const cacheKey = `i18n_${language}`;
151
+ const cachedData = this.router.cacheManager?.get(cacheKey);
152
+ if (cachedData) {
153
+ this.log("debug", "Messages loaded from cache:", language);
154
+ return cachedData;
165
155
  }
166
156
  try {
167
157
  const i18nPath = `${this.router.config.i18nPath}/${language}.json`;
@@ -170,9 +160,7 @@ var I18nManager = class {
170
160
  throw new Error(`HTTP error! status: ${response.status}`);
171
161
  }
172
162
  const messages = await response.json();
173
- if (this.config.enableDataCache) {
174
- this.saveDataToCache(language, messages);
175
- }
163
+ this.router.cacheManager?.set(cacheKey, messages);
176
164
  return messages;
177
165
  } catch (error) {
178
166
  this.log("error", "Failed to load messages file for:", language, error);
@@ -189,51 +177,6 @@ var I18nManager = class {
189
177
  return {};
190
178
  }
191
179
  }
192
- /**
193
- * 언어 데이터를 캐시에서 가져오기
194
- */
195
- getDataFromCache(language) {
196
- try {
197
- const cacheKey = `${this.config.dataCacheKey}_${language}_${this.config.cacheVersion}`;
198
- const cachedItem = localStorage.getItem(cacheKey);
199
- if (cachedItem) {
200
- const { data, timestamp, version } = JSON.parse(cachedItem);
201
- if (version !== this.config.cacheVersion) {
202
- this.log("debug", "Cache version mismatch, clearing:", language);
203
- localStorage.removeItem(cacheKey);
204
- return null;
205
- }
206
- const now = Date.now();
207
- const maxAge = 24 * 60 * 60 * 1e3;
208
- if (now - timestamp > maxAge) {
209
- this.log("debug", "Cache expired, removing:", language);
210
- localStorage.removeItem(cacheKey);
211
- return null;
212
- }
213
- return data;
214
- }
215
- } catch (error) {
216
- this.log("warn", "Failed to read from cache:", error);
217
- }
218
- return null;
219
- }
220
- /**
221
- * 언어 데이터를 캐시에 저장
222
- */
223
- saveDataToCache(language, data) {
224
- try {
225
- const cacheKey = `${this.config.dataCacheKey}_${language}_${this.config.cacheVersion}`;
226
- const cacheItem = {
227
- data,
228
- timestamp: Date.now(),
229
- version: this.config.cacheVersion
230
- };
231
- localStorage.setItem(cacheKey, JSON.stringify(cacheItem));
232
- this.log("debug", "Data saved to cache:", language);
233
- } catch (error) {
234
- this.log("warn", "Failed to save to cache:", error);
235
- }
236
- }
237
180
  /**
238
181
  * 메시지 번역
239
182
  */
@@ -286,12 +229,6 @@ var I18nManager = class {
286
229
  const pluralKey = count === 1 ? `${key}.singular` : `${key}.plural`;
287
230
  return this.t(pluralKey, { ...params, count });
288
231
  }
289
- /**
290
- * 사용 가능한 언어 목록
291
- */
292
- getAvailableLanguages() {
293
- return ["ko", "en"];
294
- }
295
232
  /**
296
233
  * 언어 변경 이벤트 리스너 등록
297
234
  */
@@ -376,49 +313,16 @@ var I18nManager = class {
376
313
  }
377
314
  }
378
315
  /**
379
- * 캐시 초기화 (버전 변경 시 사용)
316
+ * 캐시 초기화
380
317
  */
381
318
  clearCache() {
382
319
  try {
383
- const keys = Object.keys(localStorage);
384
- const cacheKeys = keys.filter((key) => key.startsWith(this.config.dataCacheKey));
385
- cacheKeys.forEach((key) => {
386
- localStorage.removeItem(key);
387
- });
388
- this.log("debug", "Cache cleared, removed", cacheKeys.length, "items");
320
+ const clearedCount = this.router.cacheManager?.deleteByPattern("i18n_");
321
+ this.log("debug", "Cache cleared, removed", clearedCount, "items");
389
322
  } catch (error) {
390
323
  this.log("warn", "Failed to clear cache:", error);
391
324
  }
392
325
  }
393
- /**
394
- * 캐시 상태 확인
395
- */
396
- getCacheInfo() {
397
- const info = {
398
- enabled: this.config.enableDataCache,
399
- version: this.config.cacheVersion,
400
- languages: {}
401
- };
402
- try {
403
- const keys = Object.keys(localStorage);
404
- const cacheKeys = keys.filter((key) => key.startsWith(this.config.dataCacheKey));
405
- cacheKeys.forEach((key) => {
406
- const match = key.match(new RegExp(`${this.config.dataCacheKey}_(w+)_(.+)`));
407
- if (match) {
408
- const [, language, version] = match;
409
- const cachedItem = JSON.parse(localStorage.getItem(key));
410
- info.languages[language] = {
411
- version,
412
- timestamp: cachedItem.timestamp,
413
- age: Date.now() - cachedItem.timestamp
414
- };
415
- }
416
- });
417
- } catch (error) {
418
- this.log("warn", "Failed to get cache info:", error);
419
- }
420
- return info;
421
- }
422
326
  /**
423
327
  * 시스템 초기화 (현재 언어의 메시지 로드)
424
328
  */
@@ -450,13 +354,9 @@ var AuthManager = class {
450
354
  publicRoutes: options.publicRoutes || ["login", "register", "home"],
451
355
  checkAuthFunction: options.checkAuthFunction || null,
452
356
  redirectAfterLogin: options.redirectAfterLogin || "home",
453
- // 쿠키/스토리지 설정
454
357
  authCookieName: options.authCookieName || "authToken",
455
- authFallbackCookieNames: options.authFallbackCookieNames || ["accessToken", "token", "jwt"],
456
- authStorage: options.authStorage || "cookie",
457
- authCookieOptions: options.authCookieOptions || {},
458
- authSkipValidation: options.authSkipValidation || false,
459
- debug: options.debug || false
358
+ authStorage: options.authStorage || "localStorage",
359
+ authSkipValidation: options.authSkipValidation || false
460
360
  };
461
361
  this.router = router;
462
362
  this.eventListeners = /* @__PURE__ */ new Map();
@@ -487,7 +387,12 @@ var AuthManager = class {
487
387
  }
488
388
  if (typeof this.config.checkAuthFunction === "function") {
489
389
  try {
490
- const isAuthenticated2 = await this.config.checkAuthFunction(routeName);
390
+ const route = {
391
+ name: routeName,
392
+ $api: this.router.routeLoader.apiHandler.bindToComponent({}),
393
+ $state: this.router.stateHandler
394
+ };
395
+ const isAuthenticated2 = await this.config.checkAuthFunction(route);
491
396
  return {
492
397
  allowed: isAuthenticated2,
493
398
  reason: isAuthenticated2 ? "custom_auth_success" : "custom_auth_failed",
@@ -498,7 +403,7 @@ var AuthManager = class {
498
403
  return { allowed: false, reason: "custom_auth_error", error };
499
404
  }
500
405
  }
501
- const isAuthenticated = this.isUserAuthenticated();
406
+ const isAuthenticated = this.isAuthenticated();
502
407
  return {
503
408
  allowed: isAuthenticated,
504
409
  reason: isAuthenticated ? "authenticated" : "not_authenticated",
@@ -506,56 +411,40 @@ var AuthManager = class {
506
411
  };
507
412
  }
508
413
  /**
509
- * 사용자 인증 상태 확인
414
+ * JWT 토큰 검증
510
415
  */
511
- isUserAuthenticated() {
512
- this.log("debug", "\u{1F50D} Checking user authentication status");
513
- const token = localStorage.getItem("authToken") || localStorage.getItem("accessToken");
514
- if (token) {
515
- try {
516
- if (token.includes(".")) {
517
- const payload = JSON.parse(atob(token.split(".")[1]));
518
- if (payload.exp && Date.now() >= payload.exp * 1e3) {
519
- this.log("debug", "localStorage token expired, removing...");
520
- localStorage.removeItem("authToken");
521
- localStorage.removeItem("accessToken");
522
- return false;
523
- }
416
+ isTokenValid(token) {
417
+ if (!token) return false;
418
+ try {
419
+ if (token.includes(".")) {
420
+ const payload = JSON.parse(atob(token.split(".")[1]));
421
+ if (payload.exp && Date.now() >= payload.exp * 1e3) {
422
+ return false;
524
423
  }
525
- this.log("debug", "\u2705 Valid token found in localStorage");
526
- return true;
527
- } catch (error) {
528
- this.log("warn", "Invalid token in localStorage:", error);
529
424
  }
530
- }
531
- const sessionToken = sessionStorage.getItem("authToken") || sessionStorage.getItem("accessToken");
532
- if (sessionToken) {
533
- this.log("debug", "\u2705 Token found in sessionStorage");
534
425
  return true;
426
+ } catch (error) {
427
+ this.log("warn", "Token validation failed:", error);
428
+ return false;
535
429
  }
536
- const authCookie = this.getAuthCookie();
537
- if (authCookie) {
538
- try {
539
- if (authCookie.includes(".")) {
540
- const payload = JSON.parse(atob(authCookie.split(".")[1]));
541
- if (payload.exp && Date.now() >= payload.exp * 1e3) {
542
- this.log("debug", "Cookie token expired, removing...");
543
- this.removeAuthCookie();
544
- return false;
545
- }
546
- }
547
- this.log("debug", "\u2705 Valid token found in cookies");
548
- return true;
549
- } catch (error) {
550
- this.log("warn", "Cookie token validation failed:", error);
551
- }
430
+ }
431
+ /**
432
+ * 사용자 인증 상태 확인
433
+ */
434
+ isAuthenticated() {
435
+ this.log("debug", "\u{1F50D} Checking user authentication status");
436
+ const token = this.getAccessToken();
437
+ if (!token) {
438
+ this.log("debug", "\u274C No token found");
439
+ return false;
552
440
  }
553
- if (window.user || window.isAuthenticated) {
554
- this.log("debug", "\u2705 Global authentication variable found");
555
- return true;
441
+ if (!this.isTokenValid(token)) {
442
+ this.log("debug", "Token expired, removing...");
443
+ this.removeAccessToken();
444
+ return false;
556
445
  }
557
- this.log("debug", "\u274C No valid authentication found");
558
- return false;
446
+ this.log("debug", "\u2705 Valid token found");
447
+ return true;
559
448
  }
560
449
  /**
561
450
  * 공개 라우트인지 확인
@@ -581,18 +470,7 @@ var AuthManager = class {
581
470
  * 인증 쿠키 가져오기
582
471
  */
583
472
  getAuthCookie() {
584
- const primaryCookie = this.getCookieValue(this.config.authCookieName);
585
- if (primaryCookie) {
586
- return primaryCookie;
587
- }
588
- for (const cookieName of this.config.authFallbackCookieNames) {
589
- const cookieValue = this.getCookieValue(cookieName);
590
- if (cookieValue) {
591
- this.log("debug", `Found auth token in fallback cookie: ${cookieName}`);
592
- return cookieValue;
593
- }
594
- }
595
- return null;
473
+ return this.getCookieValue(this.config.authCookieName);
596
474
  }
597
475
  /**
598
476
  * 쿠키 값 가져오기
@@ -609,24 +487,18 @@ var AuthManager = class {
609
487
  * 인증 쿠키 제거
610
488
  */
611
489
  removeAuthCookie() {
612
- const cookiesToRemove = [this.config.authCookieName, ...this.config.authFallbackCookieNames];
613
- cookiesToRemove.forEach((cookieName) => {
614
- document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
615
- document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${window.location.pathname};`;
616
- });
617
- this.log("debug", "Auth cookies removed");
490
+ document.cookie = `${this.config.authCookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
491
+ this.log("debug", "Auth cookie removed");
618
492
  }
619
493
  /**
620
494
  * 액세스 토큰 가져오기
621
495
  */
622
496
  getAccessToken() {
623
- let token = localStorage.getItem("authToken") || localStorage.getItem("accessToken");
497
+ let token = localStorage.getItem("authToken");
624
498
  if (token) return token;
625
- token = sessionStorage.getItem("authToken") || sessionStorage.getItem("accessToken");
499
+ token = sessionStorage.getItem("authToken");
626
500
  if (token) return token;
627
- token = this.getAuthCookie();
628
- if (token) return token;
629
- return null;
501
+ return this.getAuthCookie();
630
502
  }
631
503
  /**
632
504
  * 액세스 토큰 설정
@@ -642,17 +514,9 @@ var AuthManager = class {
642
514
  skipValidation = this.config.authSkipValidation
643
515
  } = options;
644
516
  try {
645
- if (!skipValidation && token.includes(".")) {
646
- try {
647
- const payload = JSON.parse(atob(token.split(".")[1]));
648
- if (payload.exp && Date.now() >= payload.exp * 1e3) {
649
- this.log("warn", "\u274C Token is expired");
650
- return false;
651
- }
652
- this.log("debug", "\u2705 JWT token validated");
653
- } catch (error) {
654
- this.log("warn", "\u26A0\uFE0F JWT validation failed, but proceeding:", error.message);
655
- }
517
+ if (!skipValidation && !this.isTokenValid(token)) {
518
+ this.log("warn", "\u274C Token is expired or invalid");
519
+ return false;
656
520
  }
657
521
  switch (storage) {
658
522
  case "localStorage":
@@ -664,7 +528,7 @@ var AuthManager = class {
664
528
  this.log("debug", "Token saved to sessionStorage");
665
529
  break;
666
530
  case "cookie":
667
- this.setAuthCookie(token, cookieOptions);
531
+ this.setAuthCookie(token);
668
532
  break;
669
533
  default:
670
534
  localStorage.setItem("authToken", token);
@@ -684,41 +548,25 @@ var AuthManager = class {
684
548
  /**
685
549
  * 인증 쿠키 설정
686
550
  */
687
- setAuthCookie(token, options = {}) {
688
- const {
689
- cookieName = this.config.authCookieName,
690
- secure = window.location.protocol === "https:",
691
- sameSite = "Strict",
692
- path = "/",
693
- domain = null
694
- } = options;
695
- let cookieString = `${cookieName}=${encodeURIComponent(token)}; path=${path}`;
551
+ setAuthCookie(token) {
552
+ const secure = window.location.protocol === "https:";
553
+ let cookieString = `${this.config.authCookieName}=${encodeURIComponent(token)}; path=/; SameSite=Strict`;
696
554
  if (secure) {
697
555
  cookieString += "; Secure";
698
556
  }
699
- if (sameSite) {
700
- cookieString += `; SameSite=${sameSite}`;
701
- }
702
- if (domain) {
703
- cookieString += `; Domain=${domain}`;
704
- }
705
- try {
706
- if (token.includes(".")) {
707
- try {
708
- const payload = JSON.parse(atob(token.split(".")[1]));
709
- if (payload.exp) {
710
- const expireDate = new Date(payload.exp * 1e3);
711
- cookieString += `; Expires=${expireDate.toUTCString()}`;
712
- }
713
- } catch (error) {
714
- this.log("Could not extract expiration from JWT token");
557
+ if (token.includes(".")) {
558
+ try {
559
+ const payload = JSON.parse(atob(token.split(".")[1]));
560
+ if (payload.exp) {
561
+ const expireDate = new Date(payload.exp * 1e3);
562
+ cookieString += `; Expires=${expireDate.toUTCString()}`;
715
563
  }
564
+ } catch (error) {
565
+ this.log("warn", "Could not extract expiration from JWT token");
716
566
  }
717
- } catch (error) {
718
- this.log("Token processing error:", error);
719
567
  }
720
568
  document.cookie = cookieString;
721
- this.log(`Auth cookie set: ${cookieName}`);
569
+ this.log("debug", "Auth cookie set");
722
570
  }
723
571
  /**
724
572
  * 토큰 제거
@@ -739,9 +587,7 @@ var AuthManager = class {
739
587
  case "all":
740
588
  default:
741
589
  localStorage.removeItem("authToken");
742
- localStorage.removeItem("accessToken");
743
590
  sessionStorage.removeItem("authToken");
744
- sessionStorage.removeItem("accessToken");
745
591
  this.removeAuthCookie();
746
592
  break;
747
593
  }
@@ -751,7 +597,7 @@ var AuthManager = class {
751
597
  /**
752
598
  * 로그인 성공 처리
753
599
  */
754
- handleLoginSuccess(targetRoute = null) {
600
+ loginSuccess(targetRoute = null) {
755
601
  const redirectRoute = targetRoute || this.config.redirectAfterLogin;
756
602
  this.log(`\u{1F389} Login success, redirecting to: ${redirectRoute}`);
757
603
  this.emitAuthEvent("login_success", { targetRoute: redirectRoute });
@@ -763,11 +609,9 @@ var AuthManager = class {
763
609
  /**
764
610
  * 로그아웃 처리
765
611
  */
766
- handleLogout() {
612
+ logout() {
767
613
  this.log("\u{1F44B} Logging out user");
768
614
  this.removeAccessToken();
769
- if (window.user) window.user = null;
770
- if (window.isAuthenticated) window.isAuthenticated = false;
771
615
  this.emitAuthEvent("logout", {});
772
616
  if (this.router && typeof this.router.navigateTo === "function") {
773
617
  this.router.navigateTo(this.config.loginRoute);
@@ -824,7 +668,7 @@ var AuthManager = class {
824
668
  getAuthStats() {
825
669
  return {
826
670
  enabled: this.config.enabled,
827
- isAuthenticated: this.isUserAuthenticated(),
671
+ isAuthenticated: this.isAuthenticated(),
828
672
  hasToken: !!this.getAccessToken(),
829
673
  protectedRoutesCount: this.config.protectedRoutes.length,
830
674
  protectedPrefixesCount: this.config.protectedPrefixes.length,
@@ -850,9 +694,8 @@ var CacheManager = class {
850
694
  // 'memory' 또는 'lru'
851
695
  cacheTTL: options.cacheTTL || 3e5,
852
696
  // 5분 (밀리초)
853
- maxCacheSize: options.maxCacheSize || 50,
697
+ maxCacheSize: options.maxCacheSize || 50
854
698
  // LRU 캐시 최대 크기
855
- debug: options.debug || false
856
699
  };
857
700
  this.router = router;
858
701
  this.cache = /* @__PURE__ */ new Map();
@@ -871,7 +714,7 @@ var CacheManager = class {
871
714
  /**
872
715
  * 캐시에 값 저장
873
716
  */
874
- setCache(key, value) {
717
+ set(key, value) {
875
718
  const now = Date.now();
876
719
  if (this.config.cacheMode === "lru") {
877
720
  if (this.cache.size >= this.config.maxCacheSize && !this.cache.has(key)) {
@@ -895,7 +738,7 @@ var CacheManager = class {
895
738
  /**
896
739
  * 캐시에서 값 가져오기
897
740
  */
898
- getFromCache(key) {
741
+ get(key) {
899
742
  const now = Date.now();
900
743
  const timestamp = this.cacheTimestamps.get(key);
901
744
  if (timestamp && now - timestamp > this.config.cacheTTL) {
@@ -928,13 +771,13 @@ var CacheManager = class {
928
771
  /**
929
772
  * 캐시에 키가 있는지 확인
930
773
  */
931
- hasCache(key) {
932
- return this.cache.has(key) && this.getFromCache(key) !== null;
774
+ has(key) {
775
+ return this.cache.has(key) && this.get(key) !== null;
933
776
  }
934
777
  /**
935
778
  * 특정 키 패턴의 캐시 삭제
936
779
  */
937
- invalidateByPattern(pattern) {
780
+ deleteByPattern(pattern) {
938
781
  const keysToDelete = [];
939
782
  for (const key of this.cache.keys()) {
940
783
  if (key.includes(pattern) || key.startsWith(pattern)) {
@@ -951,13 +794,13 @@ var CacheManager = class {
951
794
  }
952
795
  }
953
796
  });
954
- this.log("debug", `\u{1F9F9} Invalidated ${keysToDelete.length} cache entries matching: ${pattern}`);
797
+ this.log("debug", `\u{1F9F9} Deleted ${keysToDelete.length} cache entries matching: ${pattern}`);
955
798
  return keysToDelete.length;
956
799
  }
957
800
  /**
958
- * 특정 컴포넌트 캐시 무효화
801
+ * 특정 컴포넌트 캐시 삭제
959
802
  */
960
- invalidateComponentCache(routeName) {
803
+ deleteComponent(routeName) {
961
804
  const patterns = [
962
805
  `component_${routeName}`,
963
806
  `script_${routeName}`,
@@ -967,27 +810,27 @@ var CacheManager = class {
967
810
  ];
968
811
  let totalInvalidated = 0;
969
812
  patterns.forEach((pattern) => {
970
- totalInvalidated += this.invalidateByPattern(pattern);
813
+ totalInvalidated += this.deleteByPattern(pattern);
971
814
  });
972
- this.log(`\u{1F504} Invalidated component cache for route: ${routeName} (${totalInvalidated} entries)`);
815
+ this.log(`\u{1F504} Deleted component cache for route: ${routeName} (${totalInvalidated} entries)`);
973
816
  return totalInvalidated;
974
817
  }
975
818
  /**
976
819
  * 모든 컴포넌트 캐시 삭제
977
820
  */
978
- clearComponentCache() {
821
+ deleteAllComponents() {
979
822
  const componentPatterns = ["component_", "script_", "template_", "style_", "layout_"];
980
823
  let totalCleared = 0;
981
824
  componentPatterns.forEach((pattern) => {
982
- totalCleared += this.invalidateByPattern(pattern);
825
+ totalCleared += this.deleteByPattern(pattern);
983
826
  });
984
- this.log(`\u{1F9FD} Cleared all component caches (${totalCleared} entries)`);
827
+ this.log(`\u{1F9FD} Deleted all component caches (${totalCleared} entries)`);
985
828
  return totalCleared;
986
829
  }
987
830
  /**
988
831
  * 전체 캐시 삭제
989
832
  */
990
- clearCache() {
833
+ clearAll() {
991
834
  const size = this.cache.size;
992
835
  this.cache.clear();
993
836
  this.cacheTimestamps.clear();
@@ -998,7 +841,7 @@ var CacheManager = class {
998
841
  /**
999
842
  * 만료된 캐시 항목들 정리
1000
843
  */
1001
- cleanExpiredCache() {
844
+ cleanExpired() {
1002
845
  const now = Date.now();
1003
846
  const expiredKeys = [];
1004
847
  for (const [key, timestamp] of this.cacheTimestamps.entries()) {
@@ -1024,15 +867,15 @@ var CacheManager = class {
1024
867
  /**
1025
868
  * 캐시 통계 정보
1026
869
  */
1027
- getCacheStats() {
870
+ getStats() {
1028
871
  return {
1029
872
  size: this.cache.size,
1030
873
  maxSize: this.config.maxCacheSize,
1031
874
  mode: this.config.cacheMode,
1032
875
  ttl: this.config.cacheTTL,
1033
876
  memoryUsage: this.getMemoryUsage(),
1034
- hitRatio: this.getHitRatio(),
1035
- categories: this.getCategorizedStats()
877
+ hitRatio: this.getHitRate(),
878
+ categories: this.getStatsByCategory()
1036
879
  };
1037
880
  }
1038
881
  /**
@@ -1059,14 +902,14 @@ var CacheManager = class {
1059
902
  /**
1060
903
  * 히트 비율 계산 (간단한 추정)
1061
904
  */
1062
- getHitRatio() {
905
+ getHitRate() {
1063
906
  const ratio = this.cache.size > 0 ? Math.min(this.cache.size / this.config.maxCacheSize, 1) : 0;
1064
907
  return Math.round(ratio * 100);
1065
908
  }
1066
909
  /**
1067
910
  * 카테고리별 캐시 통계
1068
911
  */
1069
- getCategorizedStats() {
912
+ getStatsByCategory() {
1070
913
  const categories = {
1071
914
  components: 0,
1072
915
  scripts: 0,
@@ -1088,14 +931,14 @@ var CacheManager = class {
1088
931
  /**
1089
932
  * 캐시 키 목록 반환
1090
933
  */
1091
- getCacheKeys() {
934
+ getKeys() {
1092
935
  return Array.from(this.cache.keys());
1093
936
  }
1094
937
  /**
1095
938
  * 특정 패턴의 캐시 키들 반환
1096
939
  */
1097
- getCacheKeysByPattern(pattern) {
1098
- return this.getCacheKeys().filter(
940
+ getKeysByPattern(pattern) {
941
+ return this.getKeys().filter(
1099
942
  (key) => key.includes(pattern) || key.startsWith(pattern)
1100
943
  );
1101
944
  }
@@ -1107,7 +950,7 @@ var CacheManager = class {
1107
950
  clearInterval(this.cleanupInterval);
1108
951
  }
1109
952
  this.cleanupInterval = setInterval(() => {
1110
- this.cleanExpiredCache();
953
+ this.cleanExpired();
1111
954
  }, interval);
1112
955
  this.log(`\u{1F916} Auto cleanup started (interval: ${interval}ms)`);
1113
956
  }
@@ -1126,27 +969,18 @@ var CacheManager = class {
1126
969
  */
1127
970
  destroy() {
1128
971
  this.stopAutoCleanup();
1129
- this.clearCache();
972
+ this.clearAll();
1130
973
  this.log("debug", "CacheManager destroyed");
1131
974
  }
1132
975
  };
1133
976
 
1134
977
  // src/plugins/QueryManager.js
1135
978
  var QueryManager = class {
1136
- constructor(router, options = {}) {
1137
- this.config = {
1138
- enableParameterValidation: options.enableParameterValidation !== false,
1139
- logSecurityWarnings: options.logSecurityWarnings !== false,
1140
- maxParameterLength: options.maxParameterLength || 1e3,
1141
- maxArraySize: options.maxArraySize || 100,
1142
- maxParameterCount: options.maxParameterCount || 50,
1143
- allowedKeyPattern: options.allowedKeyPattern || /^[a-zA-Z0-9_\-]+$/,
1144
- debug: options.debug || false
1145
- };
979
+ constructor(router) {
1146
980
  this.router = router;
1147
981
  this.currentQueryParams = {};
1148
982
  this.currentRouteParams = {};
1149
- this.log("info", "QueryManager initialized with config:", this.config);
983
+ this.log("debug", "QueryManager initialized");
1150
984
  }
1151
985
  /**
1152
986
  * 로깅 래퍼 메서드
@@ -1156,97 +990,6 @@ var QueryManager = class {
1156
990
  this.router.errorHandler.log(level, "QueryManager", ...args);
1157
991
  }
1158
992
  }
1159
- /**
1160
- * 파라미터 값 sanitize (XSS, SQL Injection 방어)
1161
- */
1162
- sanitizeParameter(value) {
1163
- if (typeof value !== "string") return value;
1164
- let sanitized = value.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "").replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, "").replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, "").replace(/<link\b[^<]*>/gi, "").replace(/<meta\b[^<]*>/gi, "").replace(/javascript:/gi, "").replace(/vbscript:/gi, "").replace(/data:/gi, "").replace(/on\w+\s*=/gi, "").replace(/expression\s*\(/gi, "").replace(/url\s*\(/gi, "");
1165
- const sqlPatterns = [
1166
- /(\b(union|select|insert|update|delete|drop|create|alter|exec|execute|sp_|xp_)\b)/gi,
1167
- /(;|\||&|\*|%|<|>)/g,
1168
- // 위험한 특수문자
1169
- /(--|\/\*|\*\/)/g,
1170
- // SQL 주석
1171
- /(\bor\b.*\b=\b|\band\b.*\b=\b)/gi,
1172
- // OR/AND 조건문
1173
- /('.*'|".*")/g,
1174
- // 따옴표로 둘러싸인 문자열
1175
- /(\\\w+)/g
1176
- // 백슬래시 이스케이프
1177
- ];
1178
- for (const pattern of sqlPatterns) {
1179
- sanitized = sanitized.replace(pattern, "");
1180
- }
1181
- sanitized = sanitized.replace(/[<>'"&]{2,}/g, "");
1182
- if (sanitized.length > this.config.maxParameterLength) {
1183
- sanitized = sanitized.substring(0, this.config.maxParameterLength);
1184
- }
1185
- return sanitized.trim();
1186
- }
1187
- /**
1188
- * 파라미터 유효성 검증
1189
- */
1190
- validateParameter(key, value) {
1191
- if (!this.config.enableParameterValidation) {
1192
- return true;
1193
- }
1194
- if (typeof key !== "string" || key.length === 0) {
1195
- return false;
1196
- }
1197
- if (!this.config.allowedKeyPattern.test(key)) {
1198
- if (this.config.logSecurityWarnings) {
1199
- console.warn(`Invalid parameter key format: ${key}`);
1200
- }
1201
- return false;
1202
- }
1203
- if (key.length > 50) {
1204
- if (this.config.logSecurityWarnings) {
1205
- console.warn(`Parameter key too long: ${key}`);
1206
- }
1207
- return false;
1208
- }
1209
- if (value !== null && value !== void 0) {
1210
- if (typeof value === "string") {
1211
- if (value.length > this.config.maxParameterLength) {
1212
- if (this.config.logSecurityWarnings) {
1213
- console.warn(`Parameter value too long for key: ${key}`);
1214
- }
1215
- return false;
1216
- }
1217
- const dangerousPatterns = [
1218
- /<script|<iframe|<object|<embed/gi,
1219
- /javascript:|vbscript:|data:/gi,
1220
- /union.*select|insert.*into|delete.*from/gi,
1221
- /\.\.\//g,
1222
- // 경로 탐색 공격
1223
- /[<>'"&]{3,}/g
1224
- // 연속된 특수문자
1225
- ];
1226
- for (const pattern of dangerousPatterns) {
1227
- if (pattern.test(value)) {
1228
- if (this.config.logSecurityWarnings) {
1229
- console.warn(`Dangerous pattern detected in parameter ${key}:`, value);
1230
- }
1231
- return false;
1232
- }
1233
- }
1234
- } else if (Array.isArray(value)) {
1235
- if (value.length > this.config.maxArraySize) {
1236
- if (this.config.logSecurityWarnings) {
1237
- console.warn(`Parameter array too large for key: ${key}`);
1238
- }
1239
- return false;
1240
- }
1241
- for (const item of value) {
1242
- if (!this.validateParameter(`${key}[]`, item)) {
1243
- return false;
1244
- }
1245
- }
1246
- }
1247
- }
1248
- return true;
1249
- }
1250
993
  /**
1251
994
  * 쿼리스트링 파싱
1252
995
  */
@@ -1255,55 +998,21 @@ var QueryManager = class {
1255
998
  if (!queryString) return params;
1256
999
  const pairs = queryString.split("&");
1257
1000
  for (const pair of pairs) {
1001
+ const [rawKey, rawValue] = pair.split("=");
1002
+ if (!rawKey) continue;
1258
1003
  try {
1259
- const [rawKey, rawValue] = pair.split("=");
1260
- if (!rawKey) continue;
1261
- let key, value;
1262
- try {
1263
- key = decodeURIComponent(rawKey);
1264
- value = rawValue ? decodeURIComponent(rawValue) : "";
1265
- } catch (e) {
1266
- this.log("warn", "Failed to decode URI component:", pair);
1267
- continue;
1268
- }
1269
- if (!this.validateParameter(key, value)) {
1270
- this.log("warn", `Parameter rejected by security filter: ${key}`);
1271
- continue;
1272
- }
1273
- const sanitizedValue = this.sanitizeParameter(value);
1004
+ const key = decodeURIComponent(rawKey);
1005
+ const value = rawValue ? decodeURIComponent(rawValue) : "";
1274
1006
  if (key.endsWith("[]")) {
1275
1007
  const arrayKey = key.slice(0, -2);
1276
- if (!this.validateParameter(arrayKey, [])) {
1277
- continue;
1278
- }
1279
1008
  if (!params[arrayKey]) params[arrayKey] = [];
1280
- if (params[arrayKey].length < this.config.maxArraySize) {
1281
- params[arrayKey].push(sanitizedValue);
1282
- } else {
1283
- if (this.config.logSecurityWarnings) {
1284
- console.warn(`Array parameter ${arrayKey} size limit exceeded`);
1285
- }
1286
- }
1009
+ params[arrayKey].push(value);
1287
1010
  } else {
1288
- params[key] = sanitizedValue;
1011
+ params[key] = value;
1289
1012
  }
1290
1013
  } catch (error) {
1291
- this.log("error", "Error parsing query parameter:", pair, error);
1292
- }
1293
- }
1294
- const paramCount = Object.keys(params).length;
1295
- if (paramCount > this.config.maxParameterCount) {
1296
- if (this.config.logSecurityWarnings) {
1297
- console.warn(`Too many parameters (${paramCount}). Limiting to first ${this.config.maxParameterCount}.`);
1298
- }
1299
- const limitedParams = {};
1300
- let count = 0;
1301
- for (const [key, value] of Object.entries(params)) {
1302
- if (count >= this.config.maxParameterCount) break;
1303
- limitedParams[key] = value;
1304
- count++;
1014
+ this.log("warn", "Failed to decode query parameter:", pair);
1305
1015
  }
1306
- return limitedParams;
1307
1016
  }
1308
1017
  return params;
1309
1018
  }
@@ -1358,36 +1067,17 @@ var QueryManager = class {
1358
1067
  */
1359
1068
  setQueryParams(params, replace = false) {
1360
1069
  if (!params || typeof params !== "object") {
1361
- console.warn("Invalid parameters object provided to setQueryParams");
1070
+ this.log("warn", "Invalid parameters object provided to setQueryParams");
1362
1071
  return;
1363
1072
  }
1364
1073
  const currentParams = replace ? {} : { ...this.currentQueryParams };
1365
- const sanitizedParams = {};
1366
1074
  for (const [key, value] of Object.entries(params)) {
1367
- if (!this.validateParameter(key, value)) {
1368
- console.warn(`Parameter ${key} rejected by security filter`);
1369
- continue;
1370
- }
1371
- if (value !== void 0 && value !== null) {
1372
- if (Array.isArray(value)) {
1373
- sanitizedParams[key] = value.map((item) => this.sanitizeParameter(item));
1374
- } else {
1375
- sanitizedParams[key] = this.sanitizeParameter(value);
1376
- }
1377
- }
1378
- }
1379
- Object.assign(currentParams, sanitizedParams);
1380
- for (const [key, value] of Object.entries(currentParams)) {
1381
- if (value === void 0 || value === null || value === "") {
1075
+ if (value !== void 0 && value !== null && value !== "") {
1076
+ currentParams[key] = value;
1077
+ } else {
1382
1078
  delete currentParams[key];
1383
1079
  }
1384
1080
  }
1385
- const paramCount = Object.keys(currentParams).length;
1386
- if (paramCount > this.config.maxParameterCount) {
1387
- if (this.config.logSecurityWarnings) {
1388
- console.warn(`Too many parameters after update (${paramCount}). Some parameters may be dropped.`);
1389
- }
1390
- }
1391
1081
  this.currentQueryParams = currentParams;
1392
1082
  this.updateURL();
1393
1083
  }
@@ -1466,8 +1156,6 @@ var QueryManager = class {
1466
1156
  getStats() {
1467
1157
  return {
1468
1158
  currentParams: Object.keys(this.currentQueryParams).length,
1469
- maxAllowed: this.config.maxParameterCount,
1470
- validationEnabled: this.config.enableParameterValidation,
1471
1159
  currentQueryString: this.buildQueryString(this.currentQueryParams)
1472
1160
  };
1473
1161
  }
@@ -1564,9 +1252,9 @@ var FormHandler = class {
1564
1252
  const form = event.target;
1565
1253
  let action = form.getAttribute("action");
1566
1254
  const method = form.getAttribute("method") || "POST";
1567
- const successHandler = form.getAttribute("data-success-handler");
1568
- const errorHandler = form.getAttribute("data-error-handler");
1569
- const loadingHandler = form.getAttribute("data-loading-handler");
1255
+ const successHandler = form.getAttribute("data-success");
1256
+ const errorHandler = form.getAttribute("data-error");
1257
+ const loadingHandler = form.getAttribute("data-loading");
1570
1258
  const redirectTo = form.getAttribute("data-redirect");
1571
1259
  action = this.processActionParams(action, component);
1572
1260
  if (!this.validateForm(form, component)) {
@@ -1938,7 +1626,7 @@ var ComponentLoader = class {
1938
1626
  throw new Error("Component name must be a non-empty string");
1939
1627
  }
1940
1628
  const cacheKey = `component_${componentName}`;
1941
- const cachedComponent = this.router?.cacheManager?.getFromCache(cacheKey);
1629
+ const cachedComponent = this.router?.cacheManager?.get(cacheKey);
1942
1630
  if (cachedComponent) {
1943
1631
  this.log("debug", `Component '${componentName}' loaded from cache`);
1944
1632
  return cachedComponent;
@@ -1951,7 +1639,7 @@ var ComponentLoader = class {
1951
1639
  try {
1952
1640
  const component = await loadPromise;
1953
1641
  if (component && this.router?.cacheManager) {
1954
- this.router.cacheManager.setCache(cacheKey, component);
1642
+ this.router.cacheManager.set(cacheKey, component);
1955
1643
  this.log("debug", `Component '${componentName}' cached successfully`);
1956
1644
  }
1957
1645
  return component;
@@ -2025,11 +1713,11 @@ var ComponentLoader = class {
2025
1713
  async _loadProductionComponents() {
2026
1714
  try {
2027
1715
  const componentsPath = `${this.router?.config?.routesPath || "/routes"}/_components.js`;
2028
- this.log("info", "[PRODUCTION] Loading unified components from:", componentsPath);
1716
+ this.log("debug", "[PRODUCTION] Loading unified components from:", componentsPath);
2029
1717
  const componentsModule = await import(componentsPath);
2030
1718
  if (typeof componentsModule.registerComponents === "function") {
2031
1719
  this.unifiedComponents = componentsModule.components || {};
2032
- this.log("info", `[PRODUCTION] Unified components loaded: ${Object.keys(this.unifiedComponents).length} components`);
1720
+ this.log("debug", `[PRODUCTION] Unified components loaded: ${Object.keys(this.unifiedComponents).length} components`);
2033
1721
  return this.unifiedComponents;
2034
1722
  } else {
2035
1723
  throw new Error("registerComponents function not found in components module");
@@ -2047,10 +1735,10 @@ var ComponentLoader = class {
2047
1735
  const namesToLoad = componentNames || [];
2048
1736
  const components = {};
2049
1737
  if (namesToLoad.length === 0) {
2050
- this.log("info", "[DEVELOPMENT] No components to load");
1738
+ this.log("debug", "[DEVELOPMENT] No components to load");
2051
1739
  return components;
2052
1740
  }
2053
- this.log("info", `[DEVELOPMENT] Loading individual components: ${namesToLoad.join(", ")}`);
1741
+ this.log("debug", `[DEVELOPMENT] Loading individual components: ${namesToLoad.join(", ")}`);
2054
1742
  for (const name of namesToLoad) {
2055
1743
  try {
2056
1744
  const component = await this.loadComponent(name);
@@ -2061,7 +1749,7 @@ var ComponentLoader = class {
2061
1749
  this.log("warn", `[DEVELOPMENT] Failed to load component ${name}:`, loadError.message);
2062
1750
  }
2063
1751
  }
2064
- this.log("info", `[DEVELOPMENT] Individual components loaded: ${Object.keys(components).length} components`);
1752
+ this.log("debug", `[DEVELOPMENT] Individual components loaded: ${Object.keys(components).length} components`);
2065
1753
  return components;
2066
1754
  }
2067
1755
  /**
@@ -2083,7 +1771,7 @@ var ComponentLoader = class {
2083
1771
  if (!layout || typeof layout !== "string") return /* @__PURE__ */ new Set();
2084
1772
  if (!layoutName || typeof layoutName !== "string") return /* @__PURE__ */ new Set();
2085
1773
  const cacheKey = `layout_components_${layoutName}`;
2086
- const cachedComponents = this.router?.cacheManager?.getFromCache(cacheKey);
1774
+ const cachedComponents = this.router?.cacheManager?.get(cacheKey);
2087
1775
  if (cachedComponents) {
2088
1776
  this.log("debug", `Using cached layout components for '${layoutName}'`);
2089
1777
  return cachedComponents;
@@ -2091,7 +1779,7 @@ var ComponentLoader = class {
2091
1779
  const componentSet = /* @__PURE__ */ new Set();
2092
1780
  this._extractComponentsFromContent(layout, componentSet);
2093
1781
  if (this.router?.cacheManager) {
2094
- this.router.cacheManager.setCache(cacheKey, componentSet);
1782
+ this.router.cacheManager.set(cacheKey, componentSet);
2095
1783
  this.log("debug", `Cached layout components for '${layoutName}': ${Array.from(componentSet).join(", ")}`);
2096
1784
  }
2097
1785
  return componentSet;
@@ -2298,7 +1986,7 @@ ${template}`;
2298
1986
  */
2299
1987
  async createVueComponent(routeName) {
2300
1988
  const cacheKey = `component_${routeName}`;
2301
- const cached = this.router.cacheManager?.getFromCache(cacheKey);
1989
+ const cached = this.router.cacheManager?.get(cacheKey);
2302
1990
  if (cached) {
2303
1991
  return cached;
2304
1992
  }
@@ -2333,7 +2021,7 @@ ${template}`;
2333
2021
  if (!isProduction) {
2334
2022
  const layoutName = script.layout || this.config.defaultLayout;
2335
2023
  componentNames = this.componentLoader.getComponentNames(template, layout, layoutName);
2336
- this.log("info", `[DEVELOPMENT] Discovered components for route '${routeName}':`, componentNames);
2024
+ this.log("debug", `[DEVELOPMENT] Discovered components for route '${routeName}':`, componentNames);
2337
2025
  }
2338
2026
  loadedComponents = await this.componentLoader.loadAllComponents(componentNames);
2339
2027
  this.log("debug", `Components loaded successfully for route: ${routeName}`);
@@ -2353,6 +2041,7 @@ ${template}`;
2353
2041
  ...originalData,
2354
2042
  currentRoute: routeName,
2355
2043
  $query: router.queryManager?.getQueryParams() || {},
2044
+ $params: router.queryManager?.getRouteParams() || {},
2356
2045
  $lang: (() => {
2357
2046
  try {
2358
2047
  return router.i18nManager?.getCurrentLanguage() || router.config.i18nDefaultLanguage || router.config.defaultLanguage || "ko";
@@ -2374,11 +2063,12 @@ ${template}`;
2374
2063
  },
2375
2064
  async mounted() {
2376
2065
  this.$api = router.routeLoader.apiHandler.bindToComponent(this);
2066
+ this.$state = router.stateHandler;
2377
2067
  if (script.mounted) {
2378
2068
  await script.mounted.call(this);
2379
2069
  }
2380
2070
  if (script.dataURL) {
2381
- await this.$fetchData();
2071
+ await this.fetchData();
2382
2072
  }
2383
2073
  await this.$nextTick();
2384
2074
  router.routeLoader.formHandler.bindAutoForms(this);
@@ -2400,18 +2090,22 @@ ${template}`;
2400
2090
  return key;
2401
2091
  }
2402
2092
  },
2403
- // 인증 관련
2404
- $isAuthenticated: () => router.authManager?.isUserAuthenticated() || false,
2405
- $logout: () => router.authManager ? router.navigateTo(router.authManager.handleLogout()) : null,
2406
- $loginSuccess: (target) => router.authManager ? router.navigateTo(router.authManager.handleLoginSuccess(target)) : null,
2407
- $checkAuth: (route) => router.authManager ? router.authManager.checkAuthentication(route) : Promise.resolve({ allowed: true, reason: "auth_disabled" }),
2408
- $getToken: () => router.authManager?.getAccessToken() || null,
2409
- $setToken: (token, options) => router.authManager?.setAccessToken(token, options) || false,
2410
- $removeToken: (storage) => router.authManager?.removeAccessToken(storage) || null,
2411
- $getAuthCookie: () => router.authManager?.getAuthCookie() || null,
2412
- $getCookie: (name) => router.authManager?.getCookieValue(name) || null,
2093
+ // 인증 관련 (핵심 4개 메소드만)
2094
+ isAuth: () => router.authManager?.isAuthenticated() || false,
2095
+ logout: () => router.authManager ? router.navigateTo(router.authManager.logout()) : null,
2096
+ getToken: () => router.authManager?.getAccessToken() || null,
2097
+ setToken: (token, options) => router.authManager?.setAccessToken(token, options) || false,
2098
+ // i18n 언어 관리
2099
+ getLanguage: () => router.i18nManager?.getCurrentLanguage() || router.config.defaultLanguage || "ko",
2100
+ setLanguage: (lang) => router.i18nManager?.setLanguage(lang),
2101
+ // 로깅 에러 처리
2102
+ log: (level, ...args) => {
2103
+ if (router.errorHandler) {
2104
+ router.errorHandler.log(level, `[${routeName}]`, ...args);
2105
+ }
2106
+ },
2413
2107
  // 데이터 fetch (ApiHandler 래퍼)
2414
- async $fetchData(dataConfig = null) {
2108
+ async fetchData(dataConfig = null) {
2415
2109
  const configToUse = dataConfig || script.dataURL;
2416
2110
  if (!configToUse) return null;
2417
2111
  this.$dataLoading = true;
@@ -2447,7 +2141,7 @@ ${template}`;
2447
2141
  if (!isProduction && style) {
2448
2142
  component._style = style;
2449
2143
  }
2450
- this.router.cacheManager?.setCache(cacheKey, component);
2144
+ this.router.cacheManager?.set(cacheKey, component);
2451
2145
  return component;
2452
2146
  }
2453
2147
  /**
@@ -2507,7 +2201,7 @@ var ErrorHandler = class {
2507
2201
  info: 2,
2508
2202
  debug: 3
2509
2203
  };
2510
- this.log("info", "ErrorHandler", "ErrorHandler initialized with config:", this.config);
2204
+ this.log("debug", "ErrorHandler", "ErrorHandler initialized with config:", this.config);
2511
2205
  }
2512
2206
  /**
2513
2207
  * 라우트 에러 처리
@@ -2666,8 +2360,8 @@ var ErrorHandler = class {
2666
2360
  userAgent: navigator.userAgent,
2667
2361
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2668
2362
  routerConfig: {
2669
- environment: this.router.config.environment,
2670
- mode: this.router.config.mode
2363
+ environment: this.router?.config?.environment || "unknown",
2364
+ mode: this.router?.config?.mode || "unknown"
2671
2365
  }
2672
2366
  };
2673
2367
  this.error("ErrorHandler", "\uB77C\uC6B0\uD130 \uC5D0\uB7EC \uB9AC\uD3EC\uD2B8:", errorReport);
@@ -2762,6 +2456,146 @@ var ErrorHandler = class {
2762
2456
  }
2763
2457
  };
2764
2458
 
2459
+ // src/core/StateHandler.js
2460
+ var StateHandler = class {
2461
+ constructor(router) {
2462
+ this.router = router;
2463
+ this.state = {};
2464
+ this.listeners = /* @__PURE__ */ new Map();
2465
+ this.log("debug", "StateHandler initialized");
2466
+ }
2467
+ /**
2468
+ * 로깅 래퍼 메서드
2469
+ */
2470
+ log(level, ...args) {
2471
+ if (this.router?.errorHandler) {
2472
+ this.router.errorHandler.log(level, "StateHandler", ...args);
2473
+ }
2474
+ }
2475
+ /**
2476
+ * 상태 값 설정
2477
+ */
2478
+ set(key, value) {
2479
+ const oldValue = this.state[key];
2480
+ this.state[key] = value;
2481
+ this.emitChange(key, value, oldValue);
2482
+ this.log("debug", `State set: ${key}`, value);
2483
+ return value;
2484
+ }
2485
+ /**
2486
+ * 상태 값 가져오기
2487
+ */
2488
+ get(key, defaultValue = void 0) {
2489
+ const value = this.state.hasOwnProperty(key) ? this.state[key] : defaultValue;
2490
+ this.log("debug", `State get: ${key}`, value);
2491
+ return value;
2492
+ }
2493
+ /**
2494
+ * 상태 존재 확인
2495
+ */
2496
+ has(key) {
2497
+ return this.state.hasOwnProperty(key);
2498
+ }
2499
+ /**
2500
+ * 상태 삭제
2501
+ */
2502
+ delete(key) {
2503
+ if (this.has(key)) {
2504
+ const oldValue = this.state[key];
2505
+ delete this.state[key];
2506
+ this.emitChange(key, void 0, oldValue);
2507
+ this.log("debug", `State deleted: ${key}`);
2508
+ return true;
2509
+ }
2510
+ return false;
2511
+ }
2512
+ /**
2513
+ * 모든 상태 초기화
2514
+ */
2515
+ clear() {
2516
+ const keys = Object.keys(this.state);
2517
+ this.state = {};
2518
+ keys.forEach((key) => {
2519
+ this.emitChange(key, void 0, this.state[key]);
2520
+ });
2521
+ this.log("debug", "All state cleared");
2522
+ return keys.length;
2523
+ }
2524
+ /**
2525
+ * 여러 상태 한 번에 설정
2526
+ */
2527
+ update(updates) {
2528
+ if (!updates || typeof updates !== "object") {
2529
+ this.log("warn", "Invalid updates object provided");
2530
+ return;
2531
+ }
2532
+ Object.entries(updates).forEach(([key, value]) => {
2533
+ this.set(key, value);
2534
+ });
2535
+ }
2536
+ /**
2537
+ * 모든 상태 반환
2538
+ */
2539
+ getAll() {
2540
+ return { ...this.state };
2541
+ }
2542
+ /**
2543
+ * 상태 변경 리스너 등록
2544
+ */
2545
+ watch(key, callback) {
2546
+ if (!this.listeners.has(key)) {
2547
+ this.listeners.set(key, []);
2548
+ }
2549
+ this.listeners.get(key).push(callback);
2550
+ this.log("debug", `Watcher added for: ${key}`);
2551
+ }
2552
+ /**
2553
+ * 상태 변경 리스너 제거
2554
+ */
2555
+ unwatch(key, callback) {
2556
+ if (this.listeners.has(key)) {
2557
+ const callbacks = this.listeners.get(key);
2558
+ const index = callbacks.indexOf(callback);
2559
+ if (index > -1) {
2560
+ callbacks.splice(index, 1);
2561
+ this.log("debug", `Watcher removed for: ${key}`);
2562
+ }
2563
+ }
2564
+ }
2565
+ /**
2566
+ * 상태 변경 이벤트 발생
2567
+ */
2568
+ emitChange(key, newValue, oldValue) {
2569
+ if (this.listeners.has(key)) {
2570
+ this.listeners.get(key).forEach((callback) => {
2571
+ try {
2572
+ callback(newValue, oldValue, key);
2573
+ } catch (error) {
2574
+ this.log("error", "State watcher error:", error);
2575
+ }
2576
+ });
2577
+ }
2578
+ }
2579
+ /**
2580
+ * 상태 통계
2581
+ */
2582
+ getStats() {
2583
+ return {
2584
+ stateCount: Object.keys(this.state).length,
2585
+ watcherCount: Array.from(this.listeners.values()).reduce((sum, arr) => sum + arr.length, 0),
2586
+ keys: Object.keys(this.state)
2587
+ };
2588
+ }
2589
+ /**
2590
+ * 정리 (메모리 누수 방지)
2591
+ */
2592
+ destroy() {
2593
+ this.state = {};
2594
+ this.listeners.clear();
2595
+ this.log("debug", "StateHandler destroyed");
2596
+ }
2597
+ };
2598
+
2765
2599
  // src/viewlogic-router.js
2766
2600
  var ViewLogicRouter = class {
2767
2601
  constructor(options = {}) {
@@ -2809,16 +2643,8 @@ var ViewLogicRouter = class {
2809
2643
  checkAuthFunction: null,
2810
2644
  redirectAfterLogin: "home",
2811
2645
  authCookieName: "authToken",
2812
- authFallbackCookieNames: ["accessToken", "token", "jwt"],
2813
- authStorage: "cookie",
2814
- authCookieOptions: {},
2815
- authSkipValidation: false,
2816
- enableParameterValidation: true,
2817
- maxParameterLength: 1e3,
2818
- maxParameterCount: 50,
2819
- maxArraySize: 100,
2820
- allowedKeyPattern: /^[a-zA-Z0-9_-]+$/,
2821
- logSecurityWarnings: true
2646
+ authStorage: "localStorage",
2647
+ authSkipValidation: false
2822
2648
  };
2823
2649
  const config = { ...defaults, ...options };
2824
2650
  config.srcPath = this.resolvePath(config.srcPath, config.basePath);
@@ -2885,8 +2711,9 @@ var ViewLogicRouter = class {
2885
2711
  async initialize() {
2886
2712
  try {
2887
2713
  this.cacheManager = new CacheManager(this, this.config);
2714
+ this.stateHandler = new StateHandler(this);
2888
2715
  this.routeLoader = new RouteLoader(this, this.config);
2889
- this.queryManager = new QueryManager(this, this.config);
2716
+ this.queryManager = new QueryManager(this);
2890
2717
  this.errorHandler = new ErrorHandler(this, this.config);
2891
2718
  if (this.config.useI18n) {
2892
2719
  try {