@simplysm/core-common 13.0.0-beta.1

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.
Files changed (202) hide show
  1. package/.cache/typecheck-browser.tsbuildinfo +1 -0
  2. package/.cache/typecheck-node.tsbuildinfo +1 -0
  3. package/.cache/typecheck-tests-browser.tsbuildinfo +1 -0
  4. package/.cache/typecheck-tests-node.tsbuildinfo +1 -0
  5. package/README.md +887 -0
  6. package/dist/common.types.d.ts +74 -0
  7. package/dist/common.types.d.ts.map +1 -0
  8. package/dist/common.types.js +5 -0
  9. package/dist/common.types.js.map +7 -0
  10. package/dist/env.d.ts +6 -0
  11. package/dist/env.d.ts.map +1 -0
  12. package/dist/env.js +9 -0
  13. package/dist/env.js.map +7 -0
  14. package/dist/errors/argument-error.d.ts +25 -0
  15. package/dist/errors/argument-error.d.ts.map +1 -0
  16. package/dist/errors/argument-error.js +18 -0
  17. package/dist/errors/argument-error.js.map +7 -0
  18. package/dist/errors/not-implemented-error.d.ts +29 -0
  19. package/dist/errors/not-implemented-error.d.ts.map +1 -0
  20. package/dist/errors/not-implemented-error.js +14 -0
  21. package/dist/errors/not-implemented-error.js.map +7 -0
  22. package/dist/errors/sd-error.d.ts +27 -0
  23. package/dist/errors/sd-error.d.ts.map +1 -0
  24. package/dist/errors/sd-error.js +23 -0
  25. package/dist/errors/sd-error.js.map +7 -0
  26. package/dist/errors/timeout-error.d.ts +31 -0
  27. package/dist/errors/timeout-error.d.ts.map +1 -0
  28. package/dist/errors/timeout-error.js +17 -0
  29. package/dist/errors/timeout-error.js.map +7 -0
  30. package/dist/extensions/arr-ext.d.ts +15 -0
  31. package/dist/extensions/arr-ext.d.ts.map +1 -0
  32. package/dist/extensions/arr-ext.helpers.d.ts +19 -0
  33. package/dist/extensions/arr-ext.helpers.d.ts.map +1 -0
  34. package/dist/extensions/arr-ext.helpers.js +35 -0
  35. package/dist/extensions/arr-ext.helpers.js.map +7 -0
  36. package/dist/extensions/arr-ext.js +546 -0
  37. package/dist/extensions/arr-ext.js.map +7 -0
  38. package/dist/extensions/arr-ext.types.d.ts +215 -0
  39. package/dist/extensions/arr-ext.types.d.ts.map +1 -0
  40. package/dist/extensions/arr-ext.types.js +1 -0
  41. package/dist/extensions/arr-ext.types.js.map +7 -0
  42. package/dist/extensions/map-ext.d.ts +57 -0
  43. package/dist/extensions/map-ext.d.ts.map +1 -0
  44. package/dist/extensions/map-ext.js +26 -0
  45. package/dist/extensions/map-ext.js.map +7 -0
  46. package/dist/extensions/set-ext.d.ts +36 -0
  47. package/dist/extensions/set-ext.d.ts.map +1 -0
  48. package/dist/extensions/set-ext.js +29 -0
  49. package/dist/extensions/set-ext.js.map +7 -0
  50. package/dist/features/debounce-queue.d.ts +53 -0
  51. package/dist/features/debounce-queue.d.ts.map +1 -0
  52. package/dist/features/debounce-queue.js +80 -0
  53. package/dist/features/debounce-queue.js.map +7 -0
  54. package/dist/features/event-emitter.d.ts +66 -0
  55. package/dist/features/event-emitter.d.ts.map +1 -0
  56. package/dist/features/event-emitter.js +82 -0
  57. package/dist/features/event-emitter.js.map +7 -0
  58. package/dist/features/serial-queue.d.ts +47 -0
  59. package/dist/features/serial-queue.d.ts.map +1 -0
  60. package/dist/features/serial-queue.js +66 -0
  61. package/dist/features/serial-queue.js.map +7 -0
  62. package/dist/globals.d.ts +12 -0
  63. package/dist/globals.d.ts.map +1 -0
  64. package/dist/globals.js +1 -0
  65. package/dist/globals.js.map +7 -0
  66. package/dist/index.d.ts +32 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +31 -0
  69. package/dist/index.js.map +7 -0
  70. package/dist/types/date-only.d.ts +152 -0
  71. package/dist/types/date-only.d.ts.map +1 -0
  72. package/dist/types/date-only.js +251 -0
  73. package/dist/types/date-only.js.map +7 -0
  74. package/dist/types/date-time.d.ts +96 -0
  75. package/dist/types/date-time.d.ts.map +1 -0
  76. package/dist/types/date-time.js +220 -0
  77. package/dist/types/date-time.js.map +7 -0
  78. package/dist/types/lazy-gc-map.d.ts +80 -0
  79. package/dist/types/lazy-gc-map.d.ts.map +1 -0
  80. package/dist/types/lazy-gc-map.js +179 -0
  81. package/dist/types/lazy-gc-map.js.map +7 -0
  82. package/dist/types/time.d.ts +68 -0
  83. package/dist/types/time.d.ts.map +1 -0
  84. package/dist/types/time.js +151 -0
  85. package/dist/types/time.js.map +7 -0
  86. package/dist/types/uuid.d.ts +35 -0
  87. package/dist/types/uuid.d.ts.map +1 -0
  88. package/dist/types/uuid.js +71 -0
  89. package/dist/types/uuid.js.map +7 -0
  90. package/dist/utils/bytes.d.ts +51 -0
  91. package/dist/utils/bytes.d.ts.map +1 -0
  92. package/dist/utils/bytes.js +89 -0
  93. package/dist/utils/bytes.js.map +7 -0
  94. package/dist/utils/date-format.d.ts +90 -0
  95. package/dist/utils/date-format.d.ts.map +1 -0
  96. package/dist/utils/date-format.js +106 -0
  97. package/dist/utils/date-format.js.map +7 -0
  98. package/dist/utils/json.d.ts +34 -0
  99. package/dist/utils/json.d.ts.map +1 -0
  100. package/dist/utils/json.js +152 -0
  101. package/dist/utils/json.js.map +7 -0
  102. package/dist/utils/num.d.ts +60 -0
  103. package/dist/utils/num.d.ts.map +1 -0
  104. package/dist/utils/num.js +39 -0
  105. package/dist/utils/num.js.map +7 -0
  106. package/dist/utils/obj.d.ts +258 -0
  107. package/dist/utils/obj.d.ts.map +1 -0
  108. package/dist/utils/obj.js +538 -0
  109. package/dist/utils/obj.js.map +7 -0
  110. package/dist/utils/path.d.ts +23 -0
  111. package/dist/utils/path.d.ts.map +1 -0
  112. package/dist/utils/path.js +21 -0
  113. package/dist/utils/path.js.map +7 -0
  114. package/dist/utils/primitive.d.ts +18 -0
  115. package/dist/utils/primitive.d.ts.map +1 -0
  116. package/dist/utils/primitive.js +20 -0
  117. package/dist/utils/primitive.js.map +7 -0
  118. package/dist/utils/str.d.ts +103 -0
  119. package/dist/utils/str.d.ts.map +1 -0
  120. package/dist/utils/str.js +128 -0
  121. package/dist/utils/str.js.map +7 -0
  122. package/dist/utils/template-strings.d.ts +84 -0
  123. package/dist/utils/template-strings.d.ts.map +1 -0
  124. package/dist/utils/template-strings.js +49 -0
  125. package/dist/utils/template-strings.js.map +7 -0
  126. package/dist/utils/transferable.d.ts +47 -0
  127. package/dist/utils/transferable.d.ts.map +1 -0
  128. package/dist/utils/transferable.js +153 -0
  129. package/dist/utils/transferable.js.map +7 -0
  130. package/dist/utils/wait.d.ts +19 -0
  131. package/dist/utils/wait.d.ts.map +1 -0
  132. package/dist/utils/wait.js +19 -0
  133. package/dist/utils/wait.js.map +7 -0
  134. package/dist/utils/xml.d.ts +36 -0
  135. package/dist/utils/xml.d.ts.map +1 -0
  136. package/dist/utils/xml.js +51 -0
  137. package/dist/utils/xml.js.map +7 -0
  138. package/dist/zip/sd-zip.d.ts +80 -0
  139. package/dist/zip/sd-zip.d.ts.map +1 -0
  140. package/dist/zip/sd-zip.js +153 -0
  141. package/dist/zip/sd-zip.js.map +7 -0
  142. package/package.json +31 -0
  143. package/src/common.types.ts +91 -0
  144. package/src/env.ts +11 -0
  145. package/src/errors/argument-error.ts +40 -0
  146. package/src/errors/not-implemented-error.ts +32 -0
  147. package/src/errors/sd-error.ts +53 -0
  148. package/src/errors/timeout-error.ts +36 -0
  149. package/src/extensions/arr-ext.helpers.ts +53 -0
  150. package/src/extensions/arr-ext.ts +777 -0
  151. package/src/extensions/arr-ext.types.ts +258 -0
  152. package/src/extensions/map-ext.ts +86 -0
  153. package/src/extensions/set-ext.ts +68 -0
  154. package/src/features/debounce-queue.ts +116 -0
  155. package/src/features/event-emitter.ts +112 -0
  156. package/src/features/serial-queue.ts +94 -0
  157. package/src/globals.ts +12 -0
  158. package/src/index.ts +55 -0
  159. package/src/types/date-only.ts +329 -0
  160. package/src/types/date-time.ts +294 -0
  161. package/src/types/lazy-gc-map.ts +244 -0
  162. package/src/types/time.ts +210 -0
  163. package/src/types/uuid.ts +113 -0
  164. package/src/utils/bytes.ts +160 -0
  165. package/src/utils/date-format.ts +239 -0
  166. package/src/utils/json.ts +230 -0
  167. package/src/utils/num.ts +97 -0
  168. package/src/utils/obj.ts +956 -0
  169. package/src/utils/path.ts +40 -0
  170. package/src/utils/primitive.ts +33 -0
  171. package/src/utils/str.ts +252 -0
  172. package/src/utils/template-strings.ts +132 -0
  173. package/src/utils/transferable.ts +269 -0
  174. package/src/utils/wait.ts +40 -0
  175. package/src/utils/xml.ts +105 -0
  176. package/src/zip/sd-zip.ts +218 -0
  177. package/tests/errors/errors.spec.ts +196 -0
  178. package/tests/extensions/array-extension.spec.ts +790 -0
  179. package/tests/extensions/map-extension.spec.ts +147 -0
  180. package/tests/extensions/set-extension.spec.ts +74 -0
  181. package/tests/types/date-only.spec.ts +636 -0
  182. package/tests/types/date-time.spec.ts +391 -0
  183. package/tests/types/lazy-gc-map.spec.ts +692 -0
  184. package/tests/types/time.spec.ts +559 -0
  185. package/tests/types/types.spec.ts +55 -0
  186. package/tests/types/uuid.spec.ts +91 -0
  187. package/tests/utils/bytes-utils.spec.ts +230 -0
  188. package/tests/utils/date-format.spec.ts +371 -0
  189. package/tests/utils/debounce-queue.spec.ts +272 -0
  190. package/tests/utils/json.spec.ts +475 -0
  191. package/tests/utils/number.spec.ts +184 -0
  192. package/tests/utils/object.spec.ts +827 -0
  193. package/tests/utils/path.spec.ts +78 -0
  194. package/tests/utils/primitive.spec.ts +55 -0
  195. package/tests/utils/sd-event-emitter.spec.ts +216 -0
  196. package/tests/utils/serial-queue.spec.ts +365 -0
  197. package/tests/utils/string.spec.ts +294 -0
  198. package/tests/utils/template-strings.spec.ts +96 -0
  199. package/tests/utils/transferable.spec.ts +698 -0
  200. package/tests/utils/wait.spec.ts +145 -0
  201. package/tests/utils/xml.spec.ts +146 -0
  202. package/tests/zip/sd-zip.spec.ts +234 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * 자동 만료 기능이 있는 Map
3
+ * LRU 방식으로 접근 시간 갱신, 일정 시간 미접근 시 자동 삭제
4
+ *
5
+ * @note 인스턴스 사용 후 반드시 dispose()를 호출하거나 using 문을 사용해야 함.
6
+ * 그렇지 않으면 GC 타이머가 계속 동작하여 메모리 누수 발생.
7
+ *
8
+ * @example
9
+ * // using 문 사용 (권장)
10
+ * using map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 });
11
+ *
12
+ * // 또는 명시적 dispose() 호출
13
+ * const map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 });
14
+ * try {
15
+ * // ... 사용
16
+ * } finally {
17
+ * map.dispose();
18
+ * }
19
+ */
20
+ export declare class LazyGcMap<K, V> {
21
+ private readonly _options;
22
+ private static readonly _logger;
23
+ /**
24
+ * dispose() 미호출 감지용 registry
25
+ * @note FinalizationRegistry는 Chrome 84+, Node 14.6+ 지원
26
+ * 미지원 환경에서는 경고 없이 동작하지만, dispose() 미호출 시 메모리 누수 가능
27
+ */
28
+ private static readonly _registry;
29
+ private readonly _map;
30
+ private _gcTimer?;
31
+ private _isGcRunning;
32
+ private _isDestroyed;
33
+ private readonly _instanceId;
34
+ /**
35
+ * @param _options 설정 옵션
36
+ * @param _options.gcInterval GC 주기 (밀리초). 기본값: expireTime의 1/10 (최소 1000ms)
37
+ * @param _options.expireTime 만료 시간 (밀리초). 마지막 접근 후 이 시간이 지나면 삭제됨. 예: 60000 (60초)
38
+ * @param _options.onExpire 만료 시 호출되는 콜백. 비동기 함수도 가능하며, 에러 발생 시 로깅 후 계속 진행됨
39
+ */
40
+ constructor(_options: {
41
+ gcInterval?: number;
42
+ expireTime: number;
43
+ onExpire?: (key: K, value: V) => void | Promise<void>;
44
+ });
45
+ /** 저장된 항목 수 */
46
+ get size(): number;
47
+ /** 키 존재 여부 확인 (접근 시간 갱신 안함) */
48
+ has(key: K): boolean;
49
+ /** 값 조회 (접근 시간 갱신됨) */
50
+ get(key: K): V | undefined;
51
+ /** 값 저장 (접근 시간 설정 및 GC 타이머 시작) */
52
+ set(key: K, value: V): void;
53
+ /** 항목 삭제 (비었으면 GC 타이머 중지) */
54
+ delete(key: K): boolean;
55
+ /** 인스턴스 정리 (GC 타이머 중지 및 데이터 삭제) */
56
+ dispose(): void;
57
+ /** using 문 지원 */
58
+ [Symbol.dispose](): void;
59
+ /**
60
+ * 모든 항목 삭제 (인스턴스는 계속 사용 가능)
61
+ */
62
+ clear(): void;
63
+ /**
64
+ * 키에 해당하는 값을 반환하고, 없으면 factory로 생성 후 저장하여 반환
65
+ * @param key 조회할 키
66
+ * @param factory 키가 없을 때 값을 생성하는 함수
67
+ * @returns 기존 값 또는 새로 생성된 값
68
+ */
69
+ getOrCreate(key: K, factory: () => V): V;
70
+ /** 값들만 순회 (Iterator) */
71
+ values(): IterableIterator<V>;
72
+ /** 키들만 순회 (Iterator) */
73
+ keys(): IterableIterator<K>;
74
+ /** 엔트리 순회 (Iterator) */
75
+ entries(): IterableIterator<[K, V]>;
76
+ private _startGc;
77
+ private _runGc;
78
+ private _stopGc;
79
+ }
80
+ //# sourceMappingURL=lazy-gc-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy-gc-map.d.ts","sourceRoot":"","sources":["../../src/types/lazy-gc-map.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,SAAS,CAAC,CAAC,EAAE,CAAC;IAkCvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAjC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAwC;IAEvE;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAKjB;IAGhB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkD;IAGvE,OAAO,CAAC,QAAQ,CAAC,CAAiC;IAElD,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuC;IAEnE;;;;;OAKG;gBAEgB,QAAQ,EAAE;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACvD;IAKH,eAAe;IACf,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,+BAA+B;IAC/B,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAKpB,uBAAuB;IACvB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAU1B,kCAAkC;IAClC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAO3B,6BAA6B;IAC7B,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAUvB,mCAAmC;IACnC,OAAO,IAAI,IAAI;IASf,iBAAiB;IACjB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;IAIxB;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;;;;OAKG;IACH,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC;IAexC,wBAAwB;IACvB,MAAM,IAAI,gBAAgB,CAAC,CAAC,CAAC;IAO9B,wBAAwB;IACvB,IAAI,IAAI,gBAAgB,CAAC,CAAC,CAAC;IAK5B,wBAAwB;IACvB,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IASpC,OAAO,CAAC,QAAQ;YAUF,MAAM;IAmDpB,OAAO,CAAC,OAAO;CAQhB"}
@@ -0,0 +1,179 @@
1
+ import { createConsola } from "consola";
2
+ class LazyGcMap {
3
+ /**
4
+ * @param _options 설정 옵션
5
+ * @param _options.gcInterval GC 주기 (밀리초). 기본값: expireTime의 1/10 (최소 1000ms)
6
+ * @param _options.expireTime 만료 시간 (밀리초). 마지막 접근 후 이 시간이 지나면 삭제됨. 예: 60000 (60초)
7
+ * @param _options.onExpire 만료 시 호출되는 콜백. 비동기 함수도 가능하며, 에러 발생 시 로깅 후 계속 진행됨
8
+ */
9
+ constructor(_options) {
10
+ this._options = _options;
11
+ var _a;
12
+ (_a = LazyGcMap._registry) == null ? void 0 : _a.register(this, this._instanceId);
13
+ }
14
+ static _logger = createConsola().withTag("LazyGcMap");
15
+ /**
16
+ * dispose() 미호출 감지용 registry
17
+ * @note FinalizationRegistry는 Chrome 84+, Node 14.6+ 지원
18
+ * 미지원 환경에서는 경고 없이 동작하지만, dispose() 미호출 시 메모리 누수 가능
19
+ */
20
+ static _registry = typeof FinalizationRegistry !== "undefined" ? new FinalizationRegistry((id) => {
21
+ LazyGcMap._logger.warn(`LazyGcMap(${id})\uC774 dispose() \uC5C6\uC774 \uAC00\uBE44\uC9C0 \uC218\uC9D1\uB428. \uBA54\uBAA8\uB9AC \uB204\uC218 \uAC00\uB2A5\uC131 \uC788\uC74C.`);
22
+ }) : void 0;
23
+ // 실제 데이터와 마지막 접근 시간을 함께 저장
24
+ _map = /* @__PURE__ */ new Map();
25
+ // GC 타이머
26
+ _gcTimer;
27
+ // GC 실행 중 플래그 (중복 실행 방지)
28
+ _isGcRunning = false;
29
+ // destroy 호출 여부
30
+ _isDestroyed = false;
31
+ // 인스턴스 식별자 (경고 메시지용)
32
+ _instanceId = Math.random().toString(36).slice(2);
33
+ /** 저장된 항목 수 */
34
+ get size() {
35
+ return this._map.size;
36
+ }
37
+ /** 키 존재 여부 확인 (접근 시간 갱신 안함) */
38
+ has(key) {
39
+ if (this._isDestroyed) return false;
40
+ return this._map.has(key);
41
+ }
42
+ /** 값 조회 (접근 시간 갱신됨) */
43
+ get(key) {
44
+ if (this._isDestroyed) return void 0;
45
+ const item = this._map.get(key);
46
+ if (item == null) return void 0;
47
+ item.lastAccess = Date.now();
48
+ return item.value;
49
+ }
50
+ /** 값 저장 (접근 시간 설정 및 GC 타이머 시작) */
51
+ set(key, value) {
52
+ if (this._isDestroyed) return;
53
+ this._map.set(key, { value, lastAccess: Date.now() });
54
+ this._startGc();
55
+ }
56
+ /** 항목 삭제 (비었으면 GC 타이머 중지) */
57
+ delete(key) {
58
+ if (this._isDestroyed) return false;
59
+ const result = this._map.delete(key);
60
+ if (this._map.size === 0) {
61
+ this._stopGc();
62
+ }
63
+ return result;
64
+ }
65
+ /** 인스턴스 정리 (GC 타이머 중지 및 데이터 삭제) */
66
+ dispose() {
67
+ var _a;
68
+ if (this._isDestroyed) return;
69
+ this._isDestroyed = true;
70
+ (_a = LazyGcMap._registry) == null ? void 0 : _a.unregister(this);
71
+ this._map.clear();
72
+ this._stopGc();
73
+ }
74
+ /** using 문 지원 */
75
+ [Symbol.dispose]() {
76
+ this.dispose();
77
+ }
78
+ /**
79
+ * 모든 항목 삭제 (인스턴스는 계속 사용 가능)
80
+ */
81
+ clear() {
82
+ if (this._isDestroyed) return;
83
+ this._map.clear();
84
+ this._stopGc();
85
+ }
86
+ /**
87
+ * 키에 해당하는 값을 반환하고, 없으면 factory로 생성 후 저장하여 반환
88
+ * @param key 조회할 키
89
+ * @param factory 키가 없을 때 값을 생성하는 함수
90
+ * @returns 기존 값 또는 새로 생성된 값
91
+ */
92
+ getOrCreate(key, factory) {
93
+ if (this._isDestroyed) {
94
+ throw new Error("LazyGcMap\uC774 \uC774\uBBF8 dispose\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
95
+ }
96
+ const item = this._map.get(key);
97
+ if (item == null) {
98
+ const value = factory();
99
+ this.set(key, value);
100
+ return value;
101
+ }
102
+ item.lastAccess = Date.now();
103
+ return item.value;
104
+ }
105
+ /** 값들만 순회 (Iterator) */
106
+ *values() {
107
+ if (this._isDestroyed) return;
108
+ for (const item of this._map.values()) {
109
+ yield item.value;
110
+ }
111
+ }
112
+ /** 키들만 순회 (Iterator) */
113
+ *keys() {
114
+ if (this._isDestroyed) return;
115
+ yield* this._map.keys();
116
+ }
117
+ /** 엔트리 순회 (Iterator) */
118
+ *entries() {
119
+ if (this._isDestroyed) return;
120
+ for (const [key, item] of this._map.entries()) {
121
+ yield [key, item.value];
122
+ }
123
+ }
124
+ //#region GC 로직
125
+ _startGc() {
126
+ if (this._isDestroyed) return;
127
+ if (this._gcTimer != null) return;
128
+ const interval = this._options.gcInterval ?? Math.max(this._options.expireTime / 10, 1e3);
129
+ this._gcTimer = setInterval(() => {
130
+ void this._runGc();
131
+ }, interval);
132
+ }
133
+ async _runGc() {
134
+ if (this._isGcRunning) return;
135
+ this._isGcRunning = true;
136
+ try {
137
+ const now = Date.now();
138
+ const expiredEntries = [];
139
+ for (const [key, item] of this._map) {
140
+ if (now - item.lastAccess > this._options.expireTime) {
141
+ expiredEntries.push({ key, item });
142
+ }
143
+ }
144
+ for (const { key, item } of expiredEntries) {
145
+ const currentItem = this._map.get(key);
146
+ if (currentItem !== item) {
147
+ continue;
148
+ }
149
+ if (this._options.onExpire != null) {
150
+ try {
151
+ await this._options.onExpire(key, item.value);
152
+ } catch (err) {
153
+ LazyGcMap._logger.error("onExpire \uCF5C\uBC31 \uC5D0\uB7EC", err);
154
+ }
155
+ }
156
+ const afterItem = this._map.get(key);
157
+ if (afterItem === item) {
158
+ this._map.delete(key);
159
+ }
160
+ }
161
+ if (this._map.size === 0) {
162
+ this._stopGc();
163
+ }
164
+ } finally {
165
+ this._isGcRunning = false;
166
+ }
167
+ }
168
+ _stopGc() {
169
+ if (this._gcTimer != null) {
170
+ clearInterval(this._gcTimer);
171
+ this._gcTimer = void 0;
172
+ }
173
+ }
174
+ //#endregion
175
+ }
176
+ export {
177
+ LazyGcMap
178
+ };
179
+ //# sourceMappingURL=lazy-gc-map.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/types/lazy-gc-map.ts"],
4
+ "sourcesContent": ["import { createConsola } from \"consola\";\n\n/**\n * \uC790\uB3D9 \uB9CC\uB8CC \uAE30\uB2A5\uC774 \uC788\uB294 Map\n * LRU \uBC29\uC2DD\uC73C\uB85C \uC811\uADFC \uC2DC\uAC04 \uAC31\uC2E0, \uC77C\uC815 \uC2DC\uAC04 \uBBF8\uC811\uADFC \uC2DC \uC790\uB3D9 \uC0AD\uC81C\n *\n * @note \uC778\uC2A4\uD134\uC2A4 \uC0AC\uC6A9 \uD6C4 \uBC18\uB4DC\uC2DC dispose()\uB97C \uD638\uCD9C\uD558\uAC70\uB098 using \uBB38\uC744 \uC0AC\uC6A9\uD574\uC57C \uD568.\n * \uADF8\uB807\uC9C0 \uC54A\uC73C\uBA74 GC \uD0C0\uC774\uBA38\uAC00 \uACC4\uC18D \uB3D9\uC791\uD558\uC5EC \uBA54\uBAA8\uB9AC \uB204\uC218 \uBC1C\uC0DD.\n *\n * @example\n * // using \uBB38 \uC0AC\uC6A9 (\uAD8C\uC7A5)\n * using map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 });\n *\n * // \uB610\uB294 \uBA85\uC2DC\uC801 dispose() \uD638\uCD9C\n * const map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 });\n * try {\n * // ... \uC0AC\uC6A9\n * } finally {\n * map.dispose();\n * }\n */\nexport class LazyGcMap<K, V> {\n private static readonly _logger = createConsola().withTag(\"LazyGcMap\");\n\n /**\n * dispose() \uBBF8\uD638\uCD9C \uAC10\uC9C0\uC6A9 registry\n * @note FinalizationRegistry\uB294 Chrome 84+, Node 14.6+ \uC9C0\uC6D0\n * \uBBF8\uC9C0\uC6D0 \uD658\uACBD\uC5D0\uC11C\uB294 \uACBD\uACE0 \uC5C6\uC774 \uB3D9\uC791\uD558\uC9C0\uB9CC, dispose() \uBBF8\uD638\uCD9C \uC2DC \uBA54\uBAA8\uB9AC \uB204\uC218 \uAC00\uB2A5\n */\n private static readonly _registry =\n typeof FinalizationRegistry !== \"undefined\"\n ? new FinalizationRegistry<string>((id) => {\n LazyGcMap._logger.warn(`LazyGcMap(${id})\uC774 dispose() \uC5C6\uC774 \uAC00\uBE44\uC9C0 \uC218\uC9D1\uB428. \uBA54\uBAA8\uB9AC \uB204\uC218 \uAC00\uB2A5\uC131 \uC788\uC74C.`);\n })\n : undefined;\n\n // \uC2E4\uC81C \uB370\uC774\uD130\uC640 \uB9C8\uC9C0\uB9C9 \uC811\uADFC \uC2DC\uAC04\uC744 \uD568\uAED8 \uC800\uC7A5\n private readonly _map = new Map<K, { value: V; lastAccess: number }>();\n\n // GC \uD0C0\uC774\uBA38\n private _gcTimer?: ReturnType<typeof setInterval>;\n // GC \uC2E4\uD589 \uC911 \uD50C\uB798\uADF8 (\uC911\uBCF5 \uC2E4\uD589 \uBC29\uC9C0)\n private _isGcRunning = false;\n // destroy \uD638\uCD9C \uC5EC\uBD80\n private _isDestroyed = false;\n // \uC778\uC2A4\uD134\uC2A4 \uC2DD\uBCC4\uC790 (\uACBD\uACE0 \uBA54\uC2DC\uC9C0\uC6A9)\n private readonly _instanceId = Math.random().toString(36).slice(2);\n\n /**\n * @param _options \uC124\uC815 \uC635\uC158\n * @param _options.gcInterval GC \uC8FC\uAE30 (\uBC00\uB9AC\uCD08). \uAE30\uBCF8\uAC12: expireTime\uC758 1/10 (\uCD5C\uC18C 1000ms)\n * @param _options.expireTime \uB9CC\uB8CC \uC2DC\uAC04 (\uBC00\uB9AC\uCD08). \uB9C8\uC9C0\uB9C9 \uC811\uADFC \uD6C4 \uC774 \uC2DC\uAC04\uC774 \uC9C0\uB098\uBA74 \uC0AD\uC81C\uB428. \uC608: 60000 (60\uCD08)\n * @param _options.onExpire \uB9CC\uB8CC \uC2DC \uD638\uCD9C\uB418\uB294 \uCF5C\uBC31. \uBE44\uB3D9\uAE30 \uD568\uC218\uB3C4 \uAC00\uB2A5\uD558\uBA70, \uC5D0\uB7EC \uBC1C\uC0DD \uC2DC \uB85C\uAE45 \uD6C4 \uACC4\uC18D \uC9C4\uD589\uB428\n */\n constructor(\n private readonly _options: {\n gcInterval?: number;\n expireTime: number;\n onExpire?: (key: K, value: V) => void | Promise<void>;\n },\n ) {\n LazyGcMap._registry?.register(this, this._instanceId);\n }\n\n /** \uC800\uC7A5\uB41C \uD56D\uBAA9 \uC218 */\n get size(): number {\n return this._map.size;\n }\n\n /** \uD0A4 \uC874\uC7AC \uC5EC\uBD80 \uD655\uC778 (\uC811\uADFC \uC2DC\uAC04 \uAC31\uC2E0 \uC548\uD568) */\n has(key: K): boolean {\n if (this._isDestroyed) return false;\n return this._map.has(key);\n }\n\n /** \uAC12 \uC870\uD68C (\uC811\uADFC \uC2DC\uAC04 \uAC31\uC2E0\uB428) */\n get(key: K): V | undefined {\n if (this._isDestroyed) return undefined;\n const item = this._map.get(key);\n if (item == null) return undefined;\n\n // \uC811\uADFC \uC2DC \uC2DC\uAC04 \uAC31\uC2E0 (LRU)\n item.lastAccess = Date.now();\n return item.value;\n }\n\n /** \uAC12 \uC800\uC7A5 (\uC811\uADFC \uC2DC\uAC04 \uC124\uC815 \uBC0F GC \uD0C0\uC774\uBA38 \uC2DC\uC791) */\n set(key: K, value: V): void {\n if (this._isDestroyed) return;\n this._map.set(key, { value, lastAccess: Date.now() });\n // \uB370\uC774\uD130\uAC00 \uB4E4\uC5B4\uC654\uC73C\uBBC0\uB85C GC \uD0C0\uC774\uBA38 \uAC00\uB3D9\n this._startGc();\n }\n\n /** \uD56D\uBAA9 \uC0AD\uC81C (\uBE44\uC5C8\uC73C\uBA74 GC \uD0C0\uC774\uBA38 \uC911\uC9C0) */\n delete(key: K): boolean {\n if (this._isDestroyed) return false;\n const result = this._map.delete(key);\n // \uBE44\uC5C8\uC73C\uBA74 \uD0C0\uC774\uBA38 \uC911\uC9C0\n if (this._map.size === 0) {\n this._stopGc();\n }\n return result;\n }\n\n /** \uC778\uC2A4\uD134\uC2A4 \uC815\uB9AC (GC \uD0C0\uC774\uBA38 \uC911\uC9C0 \uBC0F \uB370\uC774\uD130 \uC0AD\uC81C) */\n dispose(): void {\n if (this._isDestroyed) return;\n this._isDestroyed = true;\n LazyGcMap._registry?.unregister(this);\n\n this._map.clear();\n this._stopGc();\n }\n\n /** using \uBB38 \uC9C0\uC6D0 */\n [Symbol.dispose](): void {\n this.dispose();\n }\n\n /**\n * \uBAA8\uB4E0 \uD56D\uBAA9 \uC0AD\uC81C (\uC778\uC2A4\uD134\uC2A4\uB294 \uACC4\uC18D \uC0AC\uC6A9 \uAC00\uB2A5)\n */\n clear(): void {\n if (this._isDestroyed) return;\n this._map.clear();\n this._stopGc();\n }\n\n /**\n * \uD0A4\uC5D0 \uD574\uB2F9\uD558\uB294 \uAC12\uC744 \uBC18\uD658\uD558\uACE0, \uC5C6\uC73C\uBA74 factory\uB85C \uC0DD\uC131 \uD6C4 \uC800\uC7A5\uD558\uC5EC \uBC18\uD658\n * @param key \uC870\uD68C\uD560 \uD0A4\n * @param factory \uD0A4\uAC00 \uC5C6\uC744 \uB54C \uAC12\uC744 \uC0DD\uC131\uD558\uB294 \uD568\uC218\n * @returns \uAE30\uC874 \uAC12 \uB610\uB294 \uC0C8\uB85C \uC0DD\uC131\uB41C \uAC12\n */\n getOrCreate(key: K, factory: () => V): V {\n if (this._isDestroyed) {\n throw new Error(\"LazyGcMap\uC774 \uC774\uBBF8 dispose\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\");\n }\n const item = this._map.get(key);\n if (item == null) {\n const value = factory();\n this.set(key, value);\n return value;\n }\n\n item.lastAccess = Date.now();\n return item.value;\n }\n\n /** \uAC12\uB4E4\uB9CC \uC21C\uD68C (Iterator) */\n *values(): IterableIterator<V> {\n if (this._isDestroyed) return;\n for (const item of this._map.values()) {\n yield item.value;\n }\n }\n\n /** \uD0A4\uB4E4\uB9CC \uC21C\uD68C (Iterator) */\n *keys(): IterableIterator<K> {\n if (this._isDestroyed) return;\n yield* this._map.keys();\n }\n\n /** \uC5D4\uD2B8\uB9AC \uC21C\uD68C (Iterator) */\n *entries(): IterableIterator<[K, V]> {\n if (this._isDestroyed) return;\n for (const [key, item] of this._map.entries()) {\n yield [key, item.value];\n }\n }\n\n //#region GC \uB85C\uC9C1\n\n private _startGc(): void {\n if (this._isDestroyed) return;\n if (this._gcTimer != null) return;\n\n const interval = this._options.gcInterval ?? Math.max(this._options.expireTime / 10, 1000);\n this._gcTimer = setInterval(() => {\n void this._runGc();\n }, interval);\n }\n\n private async _runGc(): Promise<void> {\n // \uC774\uBBF8 \uC2E4\uD589 \uC911\uC774\uBA74 \uC2A4\uD0B5 (onExpire \uCF5C\uBC31\uC774 \uC624\uB798 \uAC78\uB9AC\uB294 \uACBD\uC6B0 \uC911\uBCF5 \uC2E4\uD589 \uBC29\uC9C0)\n if (this._isGcRunning) return;\n this._isGcRunning = true;\n\n try {\n const now = Date.now();\n\n // 1. \uB9CC\uB8CC\uB41C \uD56D\uBAA9 \uC218\uC9D1 (\uC0AD\uC81C \uC804)\n const expiredEntries: { key: K; item: { value: V; lastAccess: number } }[] = [];\n for (const [key, item] of this._map) {\n if (now - item.lastAccess > this._options.expireTime) {\n expiredEntries.push({ key, item });\n }\n }\n\n // 2. \uAC01 \uD56D\uBAA9\uC5D0 \uB300\uD574 \uCF5C\uBC31 \uC2E4\uD589 \uD6C4 \uC0AD\uC81C\n for (const { key, item } of expiredEntries) {\n // \uCF5C\uBC31 \uC2E4\uD589 \uC804 \uD604\uC7AC \uC0C1\uD0DC \uD655\uC778 (\uC774\uBBF8 \uB2E4\uB978 \uAC12\uC73C\uB85C \uAD50\uCCB4\uB418\uC5C8\uAC70\uB098 \uC0AD\uC81C\uB418\uC5C8\uC73C\uBA74 \uC2A4\uD0B5)\n const currentItem = this._map.get(key);\n if (currentItem !== item) {\n continue;\n }\n\n // \uB9CC\uB8CC \uCF5C\uBC31 \uC2E4\uD589\n if (this._options.onExpire != null) {\n try {\n await this._options.onExpire(key, item.value);\n } catch (err) {\n LazyGcMap._logger.error(\"onExpire \uCF5C\uBC31 \uC5D0\uB7EC\", err);\n }\n }\n\n // \uCF5C\uBC31 \uD6C4 \uC7AC\uB4F1\uB85D \uC5EC\uBD80 \uD655\uC778\n // \uC2DC\uB098\uB9AC\uC624: onExpire \uCF5C\uBC31\uC5D0\uC11C \uB3D9\uC77C \uD0A4\uB85C \uC0C8 \uAC12\uC744 set()\uD55C \uACBD\uC6B0,\n // \uC0C8\uB85C \uB4F1\uB85D\uB41C \uD56D\uBAA9\uC744 \uC0AD\uC81C\uD558\uBA74 \uC548 \uB428. item \uCC38\uC870\uAC00 \uAC19\uC73C\uBA74 \uC7AC\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uAC83\uC774\uBBC0\uB85C \uC0AD\uC81C \uC9C4\uD589.\n const afterItem = this._map.get(key);\n if (afterItem === item) {\n this._map.delete(key);\n }\n }\n\n // GC \uD6C4 \uBE44\uC5C8\uC73C\uBA74 \uB044\uAE30\n if (this._map.size === 0) {\n this._stopGc();\n }\n } finally {\n this._isGcRunning = false;\n }\n }\n\n private _stopGc(): void {\n if (this._gcTimer != null) {\n clearInterval(this._gcTimer);\n this._gcTimer = undefined;\n }\n }\n\n //#endregion\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * 시간 클래스 (날짜제외: HH:mm:ss.fff, 불변)
3
+ *
4
+ * 날짜 정보 없이 시간만 저장하는 불변 클래스이다.
5
+ * 24시간을 초과하거나 음수인 경우 자동으로 정규화된다.
6
+ *
7
+ * @example
8
+ * const now = new Time();
9
+ * const specific = new Time(10, 30, 0);
10
+ * const parsed = Time.parse("10:30:00");
11
+ */
12
+ export declare class Time {
13
+ private static readonly MS_PER_DAY;
14
+ private readonly _tick;
15
+ /** 현재 시간으로 생성 */
16
+ constructor();
17
+ /** 시분초밀리초로 생성 */
18
+ constructor(hour: number, minute: number, second?: number, millisecond?: number);
19
+ /** tick (밀리초)으로 생성 */
20
+ constructor(tick: number);
21
+ /** Date 객체에서 시간 부분만 추출하여 생성 */
22
+ constructor(date: Date);
23
+ /**
24
+ * 문자열을 파싱하여 Time 인스턴스를 생성
25
+ *
26
+ * @param str 시간 문자열
27
+ * @returns 파싱된 Time 인스턴스
28
+ * @throws ArgumentError 지원하지 않는 형식인 경우
29
+ *
30
+ * @example
31
+ * Time.parse("10:30:00") // HH:mm:ss
32
+ * Time.parse("10:30:00.123") // HH:mm:ss.fff
33
+ * Time.parse("오전 10:30:00") // 오전/오후 HH:mm:ss
34
+ * Time.parse("2025-01-15T10:30:00") // ISO 8601 (시간 부분만 추출)
35
+ */
36
+ static parse(str: string): Time;
37
+ get hour(): number;
38
+ get minute(): number;
39
+ get second(): number;
40
+ get millisecond(): number;
41
+ get tick(): number;
42
+ /** 시간 세팅이 제대로 되었는지 여부 */
43
+ get isValid(): boolean;
44
+ /** 지정된 시로 새 인스턴스 반환 */
45
+ setHour(hour: number): Time;
46
+ /** 지정된 분으로 새 인스턴스 반환 */
47
+ setMinute(minute: number): Time;
48
+ /** 지정된 초로 새 인스턴스 반환 */
49
+ setSecond(second: number): Time;
50
+ /** 지정된 밀리초로 새 인스턴스 반환 */
51
+ setMillisecond(millisecond: number): Time;
52
+ /** 지정된 시간을 더한 새 인스턴스 반환 (24시간 순환) */
53
+ addHours(hours: number): Time;
54
+ /** 지정된 분을 더한 새 인스턴스 반환 (24시간 순환) */
55
+ addMinutes(minutes: number): Time;
56
+ /** 지정된 초를 더한 새 인스턴스 반환 (24시간 순환) */
57
+ addSeconds(seconds: number): Time;
58
+ /** 지정된 밀리초를 더한 새 인스턴스 반환 (24시간 순환) */
59
+ addMilliseconds(milliseconds: number): Time;
60
+ /**
61
+ * 지정된 포맷으로 문자열 변환
62
+ * @param format 포맷 문자열
63
+ * @see dtFormat 지원 포맷 문자열 참조
64
+ */
65
+ toFormatString(formatStr: string): string;
66
+ toString(): string;
67
+ }
68
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/types/time.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,qBAAa,IAAI;IACf,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAEzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B,iBAAiB;;IAEjB,iBAAiB;gBACL,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM;IAC/E,sBAAsB;gBACV,IAAI,EAAE,MAAM;IACxB,+BAA+B;gBACnB,IAAI,EAAE,IAAI;IA6BtB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAqC/B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,yBAAyB;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IAMD,uBAAuB;IACvB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3B,wBAAwB;IACxB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,uBAAuB;IACvB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,yBAAyB;IACzB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAQzC,qCAAqC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM7B,oCAAoC;IACpC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAMjC,oCAAoC;IACpC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAMjC,sCAAsC;IACtC,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAU3C;;;;OAIG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IASzC,QAAQ,IAAI,MAAM;CAKnB"}
@@ -0,0 +1,151 @@
1
+ import { ArgumentError } from "../errors/argument-error";
2
+ import { convert12To24, formatDate } from "../utils/date-format";
3
+ class Time {
4
+ static MS_PER_DAY = 24 * 60 * 60 * 1e3;
5
+ _tick;
6
+ constructor(arg1, arg2, arg3, arg4) {
7
+ if (arg1 === void 0) {
8
+ const now = /* @__PURE__ */ new Date();
9
+ this._tick = (now.getMilliseconds() + now.getSeconds() * 1e3 + now.getMinutes() * 60 * 1e3 + now.getHours() * 60 * 60 * 1e3) % Time.MS_PER_DAY;
10
+ } else if (arg2 !== void 0) {
11
+ let tick = ((arg4 ?? 0) + (arg3 ?? 0) * 1e3 + arg2 * 60 * 1e3 + arg1 * 60 * 60 * 1e3) % Time.MS_PER_DAY;
12
+ if (tick < 0) tick += Time.MS_PER_DAY;
13
+ this._tick = tick;
14
+ } else if (arg1 instanceof Date) {
15
+ this._tick = (arg1.getMilliseconds() + arg1.getSeconds() * 1e3 + arg1.getMinutes() * 60 * 1e3 + arg1.getHours() * 60 * 60 * 1e3) % Time.MS_PER_DAY;
16
+ } else {
17
+ let tick = arg1 % Time.MS_PER_DAY;
18
+ if (tick < 0) tick += Time.MS_PER_DAY;
19
+ this._tick = tick;
20
+ }
21
+ }
22
+ /**
23
+ * 문자열을 파싱하여 Time 인스턴스를 생성
24
+ *
25
+ * @param str 시간 문자열
26
+ * @returns 파싱된 Time 인스턴스
27
+ * @throws ArgumentError 지원하지 않는 형식인 경우
28
+ *
29
+ * @example
30
+ * Time.parse("10:30:00") // HH:mm:ss
31
+ * Time.parse("10:30:00.123") // HH:mm:ss.fff
32
+ * Time.parse("오전 10:30:00") // 오전/오후 HH:mm:ss
33
+ * Time.parse("2025-01-15T10:30:00") // ISO 8601 (시간 부분만 추출)
34
+ */
35
+ static parse(str) {
36
+ const match1 = /(오전|오후) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})(\.([0-9]{1,3}))?$/.exec(str);
37
+ if (match1 != null) {
38
+ const rawHour = Number(match1[2]);
39
+ const isPM = match1[1] === "\uC624\uD6C4";
40
+ const hour = convert12To24(rawHour, isPM);
41
+ return new Time(hour, Number(match1[3]), Number(match1[4]), Number(match1[6] ? match1[6].padEnd(3, "0") : "0"));
42
+ }
43
+ const match2 = /([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})(\.([0-9]{1,3}))?$/.exec(str);
44
+ if (match2 != null) {
45
+ return new Time(
46
+ Number(match2[1]),
47
+ Number(match2[2]),
48
+ Number(match2[3]),
49
+ Number(match2[5] ? match2[5].padEnd(3, "0") : "0")
50
+ );
51
+ }
52
+ const isoMatch = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.exec(str);
53
+ if (isoMatch != null) {
54
+ const date = new Date(str);
55
+ if (!Number.isNaN(date.getTime())) {
56
+ return new Time(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
57
+ }
58
+ }
59
+ throw new ArgumentError(
60
+ `\uC2DC\uAC04 \uD615\uC2DD\uC744 \uD30C\uC2F1\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC9C0\uC6D0 \uD615\uC2DD: 'HH:mm:ss', 'HH:mm:ss.fff', '\uC624\uC804/\uC624\uD6C4 HH:mm:ss', ISO 8601`,
61
+ { input: str }
62
+ );
63
+ }
64
+ //#region Getters (읽기 전용)
65
+ get hour() {
66
+ return Math.floor(this._tick / (60 * 60 * 1e3));
67
+ }
68
+ get minute() {
69
+ return Math.floor(this._tick / (60 * 1e3)) % 60;
70
+ }
71
+ get second() {
72
+ return Math.floor(this._tick / 1e3) % 60;
73
+ }
74
+ get millisecond() {
75
+ return this._tick % 1e3;
76
+ }
77
+ get tick() {
78
+ return this._tick;
79
+ }
80
+ /** 시간 세팅이 제대로 되었는지 여부 */
81
+ get isValid() {
82
+ return !Number.isNaN(this._tick);
83
+ }
84
+ //#endregion
85
+ //#region 불변 변환 메서드 (새 인스턴스 반환)
86
+ /** 지정된 시로 새 인스턴스 반환 */
87
+ setHour(hour) {
88
+ return new Time(hour, this.minute, this.second, this.millisecond);
89
+ }
90
+ /** 지정된 분으로 새 인스턴스 반환 */
91
+ setMinute(minute) {
92
+ return new Time(this.hour, minute, this.second, this.millisecond);
93
+ }
94
+ /** 지정된 초로 새 인스턴스 반환 */
95
+ setSecond(second) {
96
+ return new Time(this.hour, this.minute, second, this.millisecond);
97
+ }
98
+ /** 지정된 밀리초로 새 인스턴스 반환 */
99
+ setMillisecond(millisecond) {
100
+ return new Time(this.hour, this.minute, this.second, millisecond);
101
+ }
102
+ //#endregion
103
+ //#region 산술 메서드 (새 인스턴스 반환)
104
+ /** 지정된 시간을 더한 새 인스턴스 반환 (24시간 순환) */
105
+ addHours(hours) {
106
+ let newTick = (this._tick + hours * 60 * 60 * 1e3) % Time.MS_PER_DAY;
107
+ if (newTick < 0) newTick += Time.MS_PER_DAY;
108
+ return new Time(newTick);
109
+ }
110
+ /** 지정된 분을 더한 새 인스턴스 반환 (24시간 순환) */
111
+ addMinutes(minutes) {
112
+ let newTick = (this._tick + minutes * 60 * 1e3) % Time.MS_PER_DAY;
113
+ if (newTick < 0) newTick += Time.MS_PER_DAY;
114
+ return new Time(newTick);
115
+ }
116
+ /** 지정된 초를 더한 새 인스턴스 반환 (24시간 순환) */
117
+ addSeconds(seconds) {
118
+ let newTick = (this._tick + seconds * 1e3) % Time.MS_PER_DAY;
119
+ if (newTick < 0) newTick += Time.MS_PER_DAY;
120
+ return new Time(newTick);
121
+ }
122
+ /** 지정된 밀리초를 더한 새 인스턴스 반환 (24시간 순환) */
123
+ addMilliseconds(milliseconds) {
124
+ let newTick = (this._tick + milliseconds) % Time.MS_PER_DAY;
125
+ if (newTick < 0) newTick += Time.MS_PER_DAY;
126
+ return new Time(newTick);
127
+ }
128
+ //#endregion
129
+ //#region 포맷팅
130
+ /**
131
+ * 지정된 포맷으로 문자열 변환
132
+ * @param format 포맷 문자열
133
+ * @see dtFormat 지원 포맷 문자열 참조
134
+ */
135
+ toFormatString(formatStr) {
136
+ return formatDate(formatStr, {
137
+ hour: this.hour,
138
+ minute: this.minute,
139
+ second: this.second,
140
+ millisecond: this.millisecond
141
+ });
142
+ }
143
+ toString() {
144
+ return this.toFormatString("HH:mm:ss.fff");
145
+ }
146
+ //#endregion
147
+ }
148
+ export {
149
+ Time
150
+ };
151
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/types/time.ts"],
4
+ "sourcesContent": ["import { ArgumentError } from \"../errors/argument-error\";\nimport { convert12To24, formatDate } from \"../utils/date-format\";\n\n/**\n * \uC2DC\uAC04 \uD074\uB798\uC2A4 (\uB0A0\uC9DC\uC81C\uC678: HH:mm:ss.fff, \uBD88\uBCC0)\n *\n * \uB0A0\uC9DC \uC815\uBCF4 \uC5C6\uC774 \uC2DC\uAC04\uB9CC \uC800\uC7A5\uD558\uB294 \uBD88\uBCC0 \uD074\uB798\uC2A4\uC774\uB2E4.\n * 24\uC2DC\uAC04\uC744 \uCD08\uACFC\uD558\uAC70\uB098 \uC74C\uC218\uC778 \uACBD\uC6B0 \uC790\uB3D9\uC73C\uB85C \uC815\uADDC\uD654\uB41C\uB2E4.\n *\n * @example\n * const now = new Time();\n * const specific = new Time(10, 30, 0);\n * const parsed = Time.parse(\"10:30:00\");\n */\nexport class Time {\n private static readonly MS_PER_DAY = 24 * 60 * 60 * 1000;\n\n private readonly _tick: number;\n\n /** \uD604\uC7AC \uC2DC\uAC04\uC73C\uB85C \uC0DD\uC131 */\n constructor();\n /** \uC2DC\uBD84\uCD08\uBC00\uB9AC\uCD08\uB85C \uC0DD\uC131 */\n constructor(hour: number, minute: number, second?: number, millisecond?: number);\n /** tick (\uBC00\uB9AC\uCD08)\uC73C\uB85C \uC0DD\uC131 */\n constructor(tick: number);\n /** Date \uAC1D\uCCB4\uC5D0\uC11C \uC2DC\uAC04 \uBD80\uBD84\uB9CC \uCD94\uCD9C\uD558\uC5EC \uC0DD\uC131 */\n constructor(date: Date);\n constructor(arg1?: number | Date, arg2?: number, arg3?: number, arg4?: number) {\n if (arg1 === undefined) {\n const now = new Date();\n this._tick =\n (now.getMilliseconds() +\n now.getSeconds() * 1000 +\n now.getMinutes() * 60 * 1000 +\n now.getHours() * 60 * 60 * 1000) %\n Time.MS_PER_DAY;\n } else if (arg2 !== undefined) {\n let tick =\n ((arg4 ?? 0) + (arg3 ?? 0) * 1000 + arg2 * 60 * 1000 + (arg1 as number) * 60 * 60 * 1000) % Time.MS_PER_DAY;\n if (tick < 0) tick += Time.MS_PER_DAY;\n this._tick = tick;\n } else if (arg1 instanceof Date) {\n this._tick =\n (arg1.getMilliseconds() +\n arg1.getSeconds() * 1000 +\n arg1.getMinutes() * 60 * 1000 +\n arg1.getHours() * 60 * 60 * 1000) %\n Time.MS_PER_DAY;\n } else {\n let tick = arg1 % Time.MS_PER_DAY;\n if (tick < 0) tick += Time.MS_PER_DAY;\n this._tick = tick;\n }\n }\n\n /**\n * \uBB38\uC790\uC5F4\uC744 \uD30C\uC2F1\uD558\uC5EC Time \uC778\uC2A4\uD134\uC2A4\uB97C \uC0DD\uC131\n *\n * @param str \uC2DC\uAC04 \uBB38\uC790\uC5F4\n * @returns \uD30C\uC2F1\uB41C Time \uC778\uC2A4\uD134\uC2A4\n * @throws ArgumentError \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD615\uC2DD\uC778 \uACBD\uC6B0\n *\n * @example\n * Time.parse(\"10:30:00\") // HH:mm:ss\n * Time.parse(\"10:30:00.123\") // HH:mm:ss.fff\n * Time.parse(\"\uC624\uC804 10:30:00\") // \uC624\uC804/\uC624\uD6C4 HH:mm:ss\n * Time.parse(\"2025-01-15T10:30:00\") // ISO 8601 (\uC2DC\uAC04 \uBD80\uBD84\uB9CC \uCD94\uCD9C)\n */\n static parse(str: string): Time {\n const match1 = /(\uC624\uC804|\uC624\uD6C4) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})(\\.([0-9]{1,3}))?$/.exec(str);\n if (match1 != null) {\n const rawHour = Number(match1[2]);\n const isPM = match1[1] === \"\uC624\uD6C4\";\n const hour = convert12To24(rawHour, isPM);\n return new Time(hour, Number(match1[3]), Number(match1[4]), Number(match1[6] ? match1[6].padEnd(3, \"0\") : \"0\"));\n }\n\n const match2 = /([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})(\\.([0-9]{1,3}))?$/.exec(str);\n if (match2 != null) {\n return new Time(\n Number(match2[1]),\n Number(match2[2]),\n Number(match2[3]),\n Number(match2[5] ? match2[5].padEnd(3, \"0\") : \"0\"),\n );\n }\n\n // ISO 8601 \uD615\uC2DD (\uC608: 2025-01-15T10:30:00.123Z, 2025-01-15T10:30:00+09:00)\n // Date \uAC1D\uCCB4\uB97C \uC0AC\uC6A9\uD558\uC5EC \uD0C0\uC784\uC874 \uBCC0\uD658 \uCC98\uB9AC\n const isoMatch = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/.exec(str);\n if (isoMatch != null) {\n const date = new Date(str);\n if (!Number.isNaN(date.getTime())) {\n return new Time(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());\n }\n }\n\n throw new ArgumentError(\n `\uC2DC\uAC04 \uD615\uC2DD\uC744 \uD30C\uC2F1\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC9C0\uC6D0 \uD615\uC2DD: 'HH:mm:ss', 'HH:mm:ss.fff', '\uC624\uC804/\uC624\uD6C4 HH:mm:ss', ISO 8601`,\n { input: str },\n );\n }\n\n //#region Getters (\uC77D\uAE30 \uC804\uC6A9)\n\n get hour(): number {\n return Math.floor(this._tick / (60 * 60 * 1000));\n }\n\n get minute(): number {\n return Math.floor(this._tick / (60 * 1000)) % 60;\n }\n\n get second(): number {\n return Math.floor(this._tick / 1000) % 60;\n }\n\n get millisecond(): number {\n return this._tick % 1000;\n }\n\n get tick(): number {\n return this._tick;\n }\n\n /** \uC2DC\uAC04 \uC138\uD305\uC774 \uC81C\uB300\uB85C \uB418\uC5C8\uB294\uC9C0 \uC5EC\uBD80 */\n get isValid(): boolean {\n return !Number.isNaN(this._tick);\n }\n\n //#endregion\n\n //#region \uBD88\uBCC0 \uBCC0\uD658 \uBA54\uC11C\uB4DC (\uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658)\n\n /** \uC9C0\uC815\uB41C \uC2DC\uB85C \uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658 */\n setHour(hour: number): Time {\n return new Time(hour, this.minute, this.second, this.millisecond);\n }\n\n /** \uC9C0\uC815\uB41C \uBD84\uC73C\uB85C \uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658 */\n setMinute(minute: number): Time {\n return new Time(this.hour, minute, this.second, this.millisecond);\n }\n\n /** \uC9C0\uC815\uB41C \uCD08\uB85C \uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658 */\n setSecond(second: number): Time {\n return new Time(this.hour, this.minute, second, this.millisecond);\n }\n\n /** \uC9C0\uC815\uB41C \uBC00\uB9AC\uCD08\uB85C \uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658 */\n setMillisecond(millisecond: number): Time {\n return new Time(this.hour, this.minute, this.second, millisecond);\n }\n\n //#endregion\n\n //#region \uC0B0\uC220 \uBA54\uC11C\uB4DC (\uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658)\n\n /** \uC9C0\uC815\uB41C \uC2DC\uAC04\uC744 \uB354\uD55C \uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658 (24\uC2DC\uAC04 \uC21C\uD658) */\n addHours(hours: number): Time {\n let newTick = (this._tick + hours * 60 * 60 * 1000) % Time.MS_PER_DAY;\n if (newTick < 0) newTick += Time.MS_PER_DAY;\n return new Time(newTick);\n }\n\n /** \uC9C0\uC815\uB41C \uBD84\uC744 \uB354\uD55C \uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658 (24\uC2DC\uAC04 \uC21C\uD658) */\n addMinutes(minutes: number): Time {\n let newTick = (this._tick + minutes * 60 * 1000) % Time.MS_PER_DAY;\n if (newTick < 0) newTick += Time.MS_PER_DAY;\n return new Time(newTick);\n }\n\n /** \uC9C0\uC815\uB41C \uCD08\uB97C \uB354\uD55C \uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658 (24\uC2DC\uAC04 \uC21C\uD658) */\n addSeconds(seconds: number): Time {\n let newTick = (this._tick + seconds * 1000) % Time.MS_PER_DAY;\n if (newTick < 0) newTick += Time.MS_PER_DAY;\n return new Time(newTick);\n }\n\n /** \uC9C0\uC815\uB41C \uBC00\uB9AC\uCD08\uB97C \uB354\uD55C \uC0C8 \uC778\uC2A4\uD134\uC2A4 \uBC18\uD658 (24\uC2DC\uAC04 \uC21C\uD658) */\n addMilliseconds(milliseconds: number): Time {\n let newTick = (this._tick + milliseconds) % Time.MS_PER_DAY;\n if (newTick < 0) newTick += Time.MS_PER_DAY;\n return new Time(newTick);\n }\n\n //#endregion\n\n //#region \uD3EC\uB9F7\uD305\n\n /**\n * \uC9C0\uC815\uB41C \uD3EC\uB9F7\uC73C\uB85C \uBB38\uC790\uC5F4 \uBCC0\uD658\n * @param format \uD3EC\uB9F7 \uBB38\uC790\uC5F4\n * @see dtFormat \uC9C0\uC6D0 \uD3EC\uB9F7 \uBB38\uC790\uC5F4 \uCC38\uC870\n */\n toFormatString(formatStr: string): string {\n return formatDate(formatStr, {\n hour: this.hour,\n minute: this.minute,\n second: this.second,\n millisecond: this.millisecond,\n });\n }\n\n toString(): string {\n return this.toFormatString(\"HH:mm:ss.fff\");\n }\n\n //#endregion\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,35 @@
1
+ import type { Bytes } from "../common.types";
2
+ /**
3
+ * UUID v4 클래스
4
+ *
5
+ * crypto.getRandomValues 기반으로 암호학적으로 안전한 UUID를 생성한다. (Chrome 79+, Node.js 공용)
6
+ *
7
+ * @example
8
+ * const id = Uuid.new();
9
+ * const fromStr = new Uuid("550e8400-e29b-41d4-a716-446655440000");
10
+ */
11
+ export declare class Uuid {
12
+ private static readonly _hexTable;
13
+ private static readonly _uuidRegex;
14
+ /** 16바이트 배열을 UUID 문자열로 변환 */
15
+ private static _bytesToUuidStr;
16
+ /** 새 UUID v4 인스턴스 생성 */
17
+ static new(): Uuid;
18
+ /**
19
+ * 16바이트 Uint8Array에서 UUID 생성
20
+ * @param bytes 16바이트 배열
21
+ * @throws {ArgumentError} 바이트 크기가 16이 아닌 경우
22
+ */
23
+ static fromBytes(bytes: Bytes): Uuid;
24
+ private readonly _uuid;
25
+ /**
26
+ * @param uuid UUID 문자열 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 형식)
27
+ * @throws {ArgumentError} 형식이 올바르지 않은 경우
28
+ */
29
+ constructor(uuid: string);
30
+ /** UUID를 문자열로 변환 */
31
+ toString(): string;
32
+ /** UUID를 16바이트 Uint8Array로 변환 */
33
+ toBytes(): Bytes;
34
+ }
35
+ //# sourceMappingURL=uuid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../../src/types/uuid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAG7C;;;;;;;;GAQG;AACH,qBAAa,IAAI;IAEf,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAoF;IAErH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAqE;IAEvG,6BAA6B;IAC7B,OAAO,CAAC,MAAM,CAAC,eAAe;IA0B9B,wBAAwB;IACxB,MAAM,CAAC,GAAG,IAAI,IAAI;IAWlB;;;;OAIG;IACH,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAQpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B;;;OAGG;gBACS,IAAI,EAAE,MAAM;IAOxB,oBAAoB;IACpB,QAAQ,IAAI,MAAM;IAIlB,iCAAiC;IACjC,OAAO,IAAI,KAAK;CAuBjB"}
@@ -0,0 +1,71 @@
1
+ import { ArgumentError } from "../errors/argument-error";
2
+ class Uuid {
3
+ // 0x00 ~ 0xFF에 대한 hex 문자열 미리 계산 (256개)
4
+ static _hexTable = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
5
+ static _uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
+ /** 16바이트 배열을 UUID 문자열로 변환 */
7
+ static _bytesToUuidStr(bytes) {
8
+ const h = Uuid._hexTable;
9
+ return h[bytes[0]] + h[bytes[1]] + h[bytes[2]] + h[bytes[3]] + "-" + h[bytes[4]] + h[bytes[5]] + "-" + h[bytes[6]] + h[bytes[7]] + "-" + h[bytes[8]] + h[bytes[9]] + "-" + h[bytes[10]] + h[bytes[11]] + h[bytes[12]] + h[bytes[13]] + h[bytes[14]] + h[bytes[15]];
10
+ }
11
+ /** 새 UUID v4 인스턴스 생성 */
12
+ static new() {
13
+ const bytes = new Uint8Array(16);
14
+ crypto.getRandomValues(bytes);
15
+ bytes[6] = bytes[6] & 15 | 64;
16
+ bytes[8] = bytes[8] & 63 | 128;
17
+ return new Uuid(Uuid._bytesToUuidStr(bytes));
18
+ }
19
+ /**
20
+ * 16바이트 Uint8Array에서 UUID 생성
21
+ * @param bytes 16바이트 배열
22
+ * @throws {ArgumentError} 바이트 크기가 16이 아닌 경우
23
+ */
24
+ static fromBytes(bytes) {
25
+ if (bytes.length !== 16) {
26
+ throw new ArgumentError("UUID \uBC14\uC774\uD2B8 \uD06C\uAE30\uB294 16\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.", { length: bytes.length });
27
+ }
28
+ return new Uuid(Uuid._bytesToUuidStr(bytes));
29
+ }
30
+ _uuid;
31
+ /**
32
+ * @param uuid UUID 문자열 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 형식)
33
+ * @throws {ArgumentError} 형식이 올바르지 않은 경우
34
+ */
35
+ constructor(uuid) {
36
+ if (!Uuid._uuidRegex.test(uuid)) {
37
+ throw new ArgumentError("UUID \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", { uuid });
38
+ }
39
+ this._uuid = uuid;
40
+ }
41
+ /** UUID를 문자열로 변환 */
42
+ toString() {
43
+ return this._uuid;
44
+ }
45
+ /** UUID를 16바이트 Uint8Array로 변환 */
46
+ toBytes() {
47
+ const u = this._uuid;
48
+ return new Uint8Array([
49
+ Number.parseInt(u.substring(0, 2), 16),
50
+ Number.parseInt(u.substring(2, 4), 16),
51
+ Number.parseInt(u.substring(4, 6), 16),
52
+ Number.parseInt(u.substring(6, 8), 16),
53
+ Number.parseInt(u.substring(9, 11), 16),
54
+ Number.parseInt(u.substring(11, 13), 16),
55
+ Number.parseInt(u.substring(14, 16), 16),
56
+ Number.parseInt(u.substring(16, 18), 16),
57
+ Number.parseInt(u.substring(19, 21), 16),
58
+ Number.parseInt(u.substring(21, 23), 16),
59
+ Number.parseInt(u.substring(24, 26), 16),
60
+ Number.parseInt(u.substring(26, 28), 16),
61
+ Number.parseInt(u.substring(28, 30), 16),
62
+ Number.parseInt(u.substring(30, 32), 16),
63
+ Number.parseInt(u.substring(32, 34), 16),
64
+ Number.parseInt(u.substring(34, 36), 16)
65
+ ]);
66
+ }
67
+ }
68
+ export {
69
+ Uuid
70
+ };
71
+ //# sourceMappingURL=uuid.js.map