@simplysm/core-common 13.0.69 → 13.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -267
- package/dist/common.types.d.ts +14 -14
- package/dist/errors/argument-error.d.ts +10 -10
- package/dist/errors/argument-error.d.ts.map +1 -1
- package/dist/errors/argument-error.js +2 -2
- package/dist/errors/argument-error.js.map +1 -1
- package/dist/errors/not-implemented-error.d.ts +8 -8
- package/dist/errors/not-implemented-error.js +2 -2
- package/dist/errors/not-implemented-error.js.map +1 -1
- package/dist/errors/sd-error.d.ts +10 -10
- package/dist/errors/sd-error.d.ts.map +1 -1
- package/dist/errors/timeout-error.d.ts +10 -10
- package/dist/errors/timeout-error.js +3 -3
- package/dist/errors/timeout-error.js.map +1 -1
- package/dist/extensions/arr-ext.d.ts +2 -2
- package/dist/extensions/arr-ext.helpers.d.ts +8 -8
- package/dist/extensions/arr-ext.helpers.js +1 -1
- package/dist/extensions/arr-ext.helpers.js.map +1 -1
- package/dist/extensions/arr-ext.js +13 -13
- package/dist/extensions/arr-ext.js.map +1 -1
- package/dist/extensions/arr-ext.types.d.ts +57 -57
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/extensions/map-ext.d.ts +16 -16
- package/dist/extensions/set-ext.d.ts +11 -11
- package/dist/features/debounce-queue.d.ts +17 -15
- package/dist/features/debounce-queue.d.ts.map +1 -1
- package/dist/features/debounce-queue.js +6 -6
- package/dist/features/debounce-queue.js.map +1 -1
- package/dist/features/event-emitter.d.ts +20 -20
- package/dist/features/event-emitter.js +17 -17
- package/dist/features/serial-queue.d.ts +11 -11
- package/dist/features/serial-queue.js +5 -5
- package/dist/features/serial-queue.js.map +1 -1
- package/dist/globals.d.ts +4 -4
- package/dist/types/date-only.d.ts +64 -64
- package/dist/types/date-only.d.ts.map +1 -1
- package/dist/types/date-only.js +63 -63
- package/dist/types/date-time.d.ts +37 -37
- package/dist/types/date-time.d.ts.map +1 -1
- package/dist/types/date-time.js +54 -37
- package/dist/types/date-time.js.map +1 -1
- package/dist/types/lazy-gc-map.d.ts +26 -26
- package/dist/types/lazy-gc-map.d.ts.map +1 -1
- package/dist/types/lazy-gc-map.js +26 -26
- package/dist/types/lazy-gc-map.js.map +1 -1
- package/dist/types/time.d.ts +25 -25
- package/dist/types/time.d.ts.map +1 -1
- package/dist/types/time.js +25 -25
- package/dist/types/time.js.map +1 -1
- package/dist/types/uuid.d.ts +11 -11
- package/dist/types/uuid.d.ts.map +1 -1
- package/dist/types/uuid.js +12 -12
- package/dist/types/uuid.js.map +1 -1
- package/dist/utils/bytes.d.ts +17 -17
- package/dist/utils/bytes.js +4 -4
- package/dist/utils/bytes.js.map +1 -1
- package/dist/utils/date-format.d.ts +45 -45
- package/dist/utils/date-format.js +1 -1
- package/dist/utils/date-format.js.map +1 -1
- package/dist/utils/error.d.ts +4 -4
- package/dist/utils/json.d.ts +17 -17
- package/dist/utils/json.js +3 -3
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/num.d.ts +23 -23
- package/dist/utils/obj.d.ts +111 -111
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js +3 -3
- package/dist/utils/obj.js.map +1 -1
- package/dist/utils/path.d.ts +10 -10
- package/dist/utils/primitive.d.ts +5 -5
- package/dist/utils/primitive.js +1 -1
- package/dist/utils/primitive.js.map +1 -1
- package/dist/utils/str.d.ts +46 -46
- package/dist/utils/str.d.ts.map +1 -1
- package/dist/utils/str.js +5 -5
- package/dist/utils/str.js.map +1 -1
- package/dist/utils/template-strings.d.ts +26 -26
- package/dist/utils/transferable.d.ts +18 -18
- package/dist/utils/transferable.js +1 -1
- package/dist/utils/transferable.js.map +1 -1
- package/dist/utils/wait.d.ts +9 -9
- package/dist/utils/xml.d.ts +13 -13
- package/dist/utils/xml.d.ts.map +1 -1
- package/dist/utils/xml.js +1 -0
- package/dist/utils/xml.js.map +1 -1
- package/dist/zip/sd-zip.d.ts +22 -22
- package/dist/zip/sd-zip.js +16 -16
- package/package.json +4 -4
- package/src/common.types.ts +17 -17
- package/src/errors/argument-error.ts +15 -15
- package/src/errors/not-implemented-error.ts +9 -9
- package/src/errors/sd-error.ts +12 -12
- package/src/errors/timeout-error.ts +12 -12
- package/src/extensions/arr-ext.helpers.ts +10 -10
- package/src/extensions/arr-ext.ts +57 -57
- package/src/extensions/arr-ext.types.ts +59 -59
- package/src/extensions/map-ext.ts +16 -16
- package/src/extensions/set-ext.ts +11 -11
- package/src/features/debounce-queue.ts +21 -19
- package/src/features/event-emitter.ts +25 -25
- package/src/features/serial-queue.ts +13 -13
- package/src/globals.ts +4 -4
- package/src/index.ts +1 -1
- package/src/types/date-only.ts +83 -83
- package/src/types/date-time.ts +64 -44
- package/src/types/lazy-gc-map.ts +45 -45
- package/src/types/time.ts +34 -34
- package/src/types/uuid.ts +17 -17
- package/src/utils/bytes.ts +35 -35
- package/src/utils/date-format.ts +65 -65
- package/src/utils/error.ts +4 -4
- package/src/utils/json.ts +39 -39
- package/src/utils/num.ts +23 -23
- package/src/utils/obj.ts +138 -138
- package/src/utils/path.ts +10 -10
- package/src/utils/primitive.ts +6 -6
- package/src/utils/str.ts +260 -261
- package/src/utils/template-strings.ts +29 -29
- package/src/utils/transferable.ts +284 -284
- package/src/utils/wait.ts +10 -10
- package/src/utils/xml.ts +20 -19
- package/src/zip/sd-zip.ts +25 -25
- package/tests/errors/errors.spec.ts +80 -0
- package/tests/extensions/array-extension.spec.ts +796 -0
- package/tests/extensions/map-extension.spec.ts +147 -0
- package/tests/extensions/set-extension.spec.ts +74 -0
- package/tests/types/date-only.spec.ts +638 -0
- package/tests/types/date-time.spec.ts +391 -0
- package/tests/types/lazy-gc-map.spec.ts +692 -0
- package/tests/types/time.spec.ts +559 -0
- package/tests/types/uuid.spec.ts +74 -0
- package/tests/utils/bytes-utils.spec.ts +230 -0
- package/tests/utils/date-format.spec.ts +373 -0
- package/tests/utils/debounce-queue.spec.ts +272 -0
- package/tests/utils/json.spec.ts +486 -0
- package/tests/utils/number.spec.ts +157 -0
- package/tests/utils/object.spec.ts +829 -0
- package/tests/utils/path.spec.ts +78 -0
- package/tests/utils/primitive.spec.ts +43 -0
- package/tests/utils/sd-event-emitter.spec.ts +216 -0
- package/tests/utils/serial-queue.spec.ts +365 -0
- package/tests/utils/string.spec.ts +281 -0
- package/tests/utils/template-strings.spec.ts +57 -0
- package/tests/utils/transferable.spec.ts +703 -0
- package/tests/utils/wait.spec.ts +145 -0
- package/tests/utils/xml.spec.ts +146 -0
- package/tests/zip/sd-zip.spec.ts +238 -0
- package/docs/extensions.md +0 -503
- package/docs/features.md +0 -109
- package/docs/types.md +0 -486
- package/docs/utils.md +0 -780
package/src/types/lazy-gc-map.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import consola from "consola";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Map with automatic expiration feature
|
|
5
|
+
* Updates access time in LRU manner, auto-deletes if not accessed for specified time
|
|
6
6
|
*
|
|
7
|
-
* @note
|
|
8
|
-
*
|
|
7
|
+
* @note Must call dispose() or use 'using' statement after use.
|
|
8
|
+
* Otherwise GC timer continues running and causes memory leak.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
|
-
* // using
|
|
11
|
+
* // using statement (recommended)
|
|
12
12
|
* using map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 });
|
|
13
13
|
*
|
|
14
|
-
* //
|
|
14
|
+
* // Or explicit dispose() call
|
|
15
15
|
* const map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 });
|
|
16
16
|
* try {
|
|
17
|
-
* // ...
|
|
17
|
+
* // ... usage
|
|
18
18
|
* } finally {
|
|
19
19
|
* map.dispose();
|
|
20
20
|
* }
|
|
@@ -22,21 +22,21 @@ import consola from "consola";
|
|
|
22
22
|
export class LazyGcMap<TKey, TValue> {
|
|
23
23
|
private static readonly _logger = consola.withTag("LazyGcMap");
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// Store actual data and last access time together
|
|
26
26
|
private readonly _map = new Map<TKey, { value: TValue; lastAccess: number }>();
|
|
27
27
|
|
|
28
|
-
// GC
|
|
28
|
+
// GC timer
|
|
29
29
|
private _gcTimer?: ReturnType<typeof setInterval>;
|
|
30
|
-
//
|
|
30
|
+
// Flag to prevent duplicate GC runs
|
|
31
31
|
private _isGcRunning = false;
|
|
32
|
-
//
|
|
32
|
+
// Whether dispose() has been called
|
|
33
33
|
private _isDestroyed = false;
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* @param _options
|
|
37
|
-
* @param _options.gcInterval GC
|
|
38
|
-
* @param _options.expireTime
|
|
39
|
-
* @param _options.onExpire
|
|
36
|
+
* @param _options Configuration options
|
|
37
|
+
* @param _options.gcInterval GC interval in milliseconds. Default: 1/10 of expireTime (minimum 1000ms)
|
|
38
|
+
* @param _options.expireTime Expiration time in milliseconds. Deleted after this time since last access. Example: 60000 (60 seconds)
|
|
39
|
+
* @param _options.onExpire Callback called on expiration. Can be async function, errors are logged and continue execution
|
|
40
40
|
*/
|
|
41
41
|
constructor(
|
|
42
42
|
private readonly _options: {
|
|
@@ -46,48 +46,48 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
46
46
|
},
|
|
47
47
|
) {}
|
|
48
48
|
|
|
49
|
-
/**
|
|
49
|
+
/** Number of stored items */
|
|
50
50
|
get size(): number {
|
|
51
51
|
return this._map.size;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/**
|
|
54
|
+
/** Check if key exists (does not update access time) */
|
|
55
55
|
has(key: TKey): boolean {
|
|
56
56
|
if (this._isDestroyed) return false;
|
|
57
57
|
return this._map.has(key);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/**
|
|
60
|
+
/** Get value (updates access time) */
|
|
61
61
|
get(key: TKey): TValue | undefined {
|
|
62
62
|
if (this._isDestroyed) return undefined;
|
|
63
63
|
const item = this._map.get(key);
|
|
64
64
|
if (item == null) return undefined;
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// Update access time on retrieval (LRU)
|
|
67
67
|
item.lastAccess = Date.now();
|
|
68
68
|
return item.value;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
/**
|
|
71
|
+
/** Store value (sets access time and starts GC timer) */
|
|
72
72
|
set(key: TKey, value: TValue): void {
|
|
73
73
|
if (this._isDestroyed) return;
|
|
74
74
|
this._map.set(key, { value, lastAccess: Date.now() });
|
|
75
|
-
//
|
|
75
|
+
// Start GC timer when data is added
|
|
76
76
|
this._startGc();
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/**
|
|
79
|
+
/** Delete item (stops GC timer if empty) */
|
|
80
80
|
delete(key: TKey): boolean {
|
|
81
81
|
if (this._isDestroyed) return false;
|
|
82
82
|
const result = this._map.delete(key);
|
|
83
|
-
//
|
|
83
|
+
// Stop timer if empty
|
|
84
84
|
if (this._map.size === 0) {
|
|
85
85
|
this._stopGc();
|
|
86
86
|
}
|
|
87
87
|
return result;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/**
|
|
90
|
+
/** Clean up instance (stop GC timer and delete data) */
|
|
91
91
|
dispose(): void {
|
|
92
92
|
if (this._isDestroyed) return;
|
|
93
93
|
this._isDestroyed = true;
|
|
@@ -95,13 +95,13 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
95
95
|
this._stopGc();
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/** using
|
|
98
|
+
/** Support for 'using' statement */
|
|
99
99
|
[Symbol.dispose](): void {
|
|
100
100
|
this.dispose();
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
|
-
*
|
|
104
|
+
* Delete all items (instance can still be used)
|
|
105
105
|
*/
|
|
106
106
|
clear(): void {
|
|
107
107
|
if (this._isDestroyed) return;
|
|
@@ -110,14 +110,14 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
* @param key
|
|
115
|
-
* @param factory
|
|
116
|
-
* @returns
|
|
113
|
+
* Return value for key, or create and store via factory if not found
|
|
114
|
+
* @param key Key to look up
|
|
115
|
+
* @param factory Function to create value if key not found
|
|
116
|
+
* @returns Existing or newly created value
|
|
117
117
|
*/
|
|
118
118
|
getOrCreate(key: TKey, factory: () => TValue): TValue {
|
|
119
119
|
if (this._isDestroyed) {
|
|
120
|
-
throw new Error("LazyGcMap
|
|
120
|
+
throw new Error("LazyGcMap has already been disposed.");
|
|
121
121
|
}
|
|
122
122
|
const item = this._map.get(key);
|
|
123
123
|
if (item == null) {
|
|
@@ -130,7 +130,7 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
130
130
|
return item.value;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
/**
|
|
133
|
+
/** Iterate over values only (Iterator) */
|
|
134
134
|
*values(): IterableIterator<TValue> {
|
|
135
135
|
if (this._isDestroyed) return;
|
|
136
136
|
for (const item of this._map.values()) {
|
|
@@ -138,13 +138,13 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
/**
|
|
141
|
+
/** Iterate over keys only (Iterator) */
|
|
142
142
|
*keys(): IterableIterator<TKey> {
|
|
143
143
|
if (this._isDestroyed) return;
|
|
144
144
|
yield* this._map.keys();
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/**
|
|
147
|
+
/** Iterate over entries (Iterator) */
|
|
148
148
|
*entries(): IterableIterator<[TKey, TValue]> {
|
|
149
149
|
if (this._isDestroyed) return;
|
|
150
150
|
for (const [key, item] of this._map.entries()) {
|
|
@@ -152,7 +152,7 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
//#region GC
|
|
155
|
+
//#region GC logic
|
|
156
156
|
|
|
157
157
|
private _startGc(): void {
|
|
158
158
|
if (this._isDestroyed) return;
|
|
@@ -165,14 +165,14 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
private async _runGc(): Promise<void> {
|
|
168
|
-
//
|
|
168
|
+
// Skip if already running (prevent duplicate runs when onExpire callback takes time)
|
|
169
169
|
if (this._isGcRunning) return;
|
|
170
170
|
this._isGcRunning = true;
|
|
171
171
|
|
|
172
172
|
try {
|
|
173
173
|
const now = Date.now();
|
|
174
174
|
|
|
175
|
-
// 1.
|
|
175
|
+
// 1. Collect expired items (before deletion)
|
|
176
176
|
const expiredEntries: { key: TKey; item: { value: TValue; lastAccess: number } }[] = [];
|
|
177
177
|
for (const [key, item] of this._map) {
|
|
178
178
|
if (now - item.lastAccess > this._options.expireTime) {
|
|
@@ -180,33 +180,33 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
// 2.
|
|
183
|
+
// 2. Run callback then delete each item
|
|
184
184
|
for (const { key, item } of expiredEntries) {
|
|
185
|
-
//
|
|
185
|
+
// Check current state before callback (skip if already replaced or deleted)
|
|
186
186
|
const currentItem = this._map.get(key);
|
|
187
187
|
if (currentItem !== item) {
|
|
188
188
|
continue;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
//
|
|
191
|
+
// Run expiration callback
|
|
192
192
|
if (this._options.onExpire != null) {
|
|
193
193
|
try {
|
|
194
194
|
await this._options.onExpire(key, item.value);
|
|
195
195
|
} catch (err) {
|
|
196
|
-
LazyGcMap._logger.error("onExpire
|
|
196
|
+
LazyGcMap._logger.error("onExpire callback error", err);
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
200
|
+
// Check if item was re-registered after callback
|
|
201
|
+
// Scenario: onExpire callback calls set() with same key,
|
|
202
|
+
// we should not delete newly registered item. If item reference is same, not re-registered, so delete.
|
|
203
203
|
const afterItem = this._map.get(key);
|
|
204
204
|
if (afterItem === item) {
|
|
205
205
|
this._map.delete(key);
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
// GC
|
|
209
|
+
// Stop GC if empty after cleanup
|
|
210
210
|
if (this._map.size === 0) {
|
|
211
211
|
this._stopGc();
|
|
212
212
|
}
|
package/src/types/time.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { ArgumentError } from "../errors/argument-error";
|
|
|
2
2
|
import { convert12To24, formatDate } from "../utils/date-format";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Time class (without date: HH:mm:ss.fff, immutable)
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* 24
|
|
7
|
+
* An immutable class that stores only time without date information.
|
|
8
|
+
* Values exceeding 24 hours or negative values are automatically normalized.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* const now = new Time();
|
|
@@ -17,13 +17,13 @@ export class Time {
|
|
|
17
17
|
|
|
18
18
|
private readonly _tick: number;
|
|
19
19
|
|
|
20
|
-
/**
|
|
20
|
+
/** Create with current time */
|
|
21
21
|
constructor();
|
|
22
|
-
/**
|
|
22
|
+
/** Create with hour, minute, second, millisecond */
|
|
23
23
|
constructor(hour: number, minute: number, second?: number, millisecond?: number);
|
|
24
|
-
/** tick (
|
|
24
|
+
/** Create from tick (millisecond) */
|
|
25
25
|
constructor(tick: number);
|
|
26
|
-
/**
|
|
26
|
+
/** Create by extracting time part only from Date object */
|
|
27
27
|
constructor(date: Date);
|
|
28
28
|
constructor(arg1?: number | Date, arg2?: number, arg3?: number, arg4?: number) {
|
|
29
29
|
if (arg1 === undefined) {
|
|
@@ -55,23 +55,23 @@ export class Time {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* Parse a string to create Time instance
|
|
59
59
|
*
|
|
60
|
-
* @param str
|
|
61
|
-
* @returns
|
|
62
|
-
* @throws ArgumentError
|
|
60
|
+
* @param str Time string
|
|
61
|
+
* @returns Parsed Time instance
|
|
62
|
+
* @throws ArgumentError If unsupported format
|
|
63
63
|
*
|
|
64
64
|
* @example
|
|
65
65
|
* Time.parse("10:30:00") // HH:mm:ss
|
|
66
66
|
* Time.parse("10:30:00.123") // HH:mm:ss.fff
|
|
67
|
-
* Time.parse("
|
|
68
|
-
* Time.parse("2025-01-15T10:30:00") // ISO 8601 (
|
|
67
|
+
* Time.parse("AM 10:30:00") // AM/PM HH:mm:ss
|
|
68
|
+
* Time.parse("2025-01-15T10:30:00") // ISO 8601 (extract time part only)
|
|
69
69
|
*/
|
|
70
70
|
static parse(str: string): Time {
|
|
71
|
-
const match1 = /(
|
|
71
|
+
const match1 = /(AM|PM) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})(\.([0-9]{1,3}))?$/i.exec(str);
|
|
72
72
|
if (match1 != null) {
|
|
73
73
|
const rawHour = Number(match1[2]);
|
|
74
|
-
const isPM = match1[1] === "
|
|
74
|
+
const isPM = match1[1].toUpperCase() === "PM";
|
|
75
75
|
const hour = convert12To24(rawHour, isPM);
|
|
76
76
|
return new Time(
|
|
77
77
|
hour,
|
|
@@ -91,8 +91,8 @@ export class Time {
|
|
|
91
91
|
);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// ISO 8601
|
|
95
|
-
// Date
|
|
94
|
+
// ISO 8601 format (e.g., 2025-01-15T10:30:00.123Z, 2025-01-15T10:30:00+09:00)
|
|
95
|
+
// Use Date object to handle timezone conversion
|
|
96
96
|
const isoMatch = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.exec(str);
|
|
97
97
|
if (isoMatch != null) {
|
|
98
98
|
const date = new Date(str);
|
|
@@ -107,12 +107,12 @@ export class Time {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
throw new ArgumentError(
|
|
110
|
-
|
|
110
|
+
`Failed to parse time format. Supported formats: 'HH:mm:ss', 'HH:mm:ss.fff', 'AM/PM HH:mm:ss', ISO 8601`,
|
|
111
111
|
{ input: str },
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
//#region Getters (
|
|
115
|
+
//#region Getters (read-only)
|
|
116
116
|
|
|
117
117
|
get hour(): number {
|
|
118
118
|
return Math.floor(this._tick / (60 * 60 * 1000));
|
|
@@ -134,61 +134,61 @@ export class Time {
|
|
|
134
134
|
return this._tick;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/**
|
|
137
|
+
/** Whether time is set correctly */
|
|
138
138
|
get isValid(): boolean {
|
|
139
139
|
return !Number.isNaN(this._tick);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
//#endregion
|
|
143
143
|
|
|
144
|
-
//#region
|
|
144
|
+
//#region Immutable transformation methods (returns new instance)
|
|
145
145
|
|
|
146
|
-
/**
|
|
146
|
+
/** Return new instance with specified hour */
|
|
147
147
|
setHour(hour: number): Time {
|
|
148
148
|
return new Time(hour, this.minute, this.second, this.millisecond);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
/**
|
|
151
|
+
/** Return new instance with specified minute */
|
|
152
152
|
setMinute(minute: number): Time {
|
|
153
153
|
return new Time(this.hour, minute, this.second, this.millisecond);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
/**
|
|
156
|
+
/** Return new instance with specified second */
|
|
157
157
|
setSecond(second: number): Time {
|
|
158
158
|
return new Time(this.hour, this.minute, second, this.millisecond);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/**
|
|
161
|
+
/** Return new instance with specified millisecond */
|
|
162
162
|
setMillisecond(millisecond: number): Time {
|
|
163
163
|
return new Time(this.hour, this.minute, this.second, millisecond);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
//#endregion
|
|
167
167
|
|
|
168
|
-
//#region
|
|
168
|
+
//#region Arithmetic methods (returns new instance)
|
|
169
169
|
|
|
170
|
-
/**
|
|
170
|
+
/** Return new instance with specified hours added (24-hour wraparound) */
|
|
171
171
|
addHours(hours: number): Time {
|
|
172
172
|
let newTick = (this._tick + hours * 60 * 60 * 1000) % Time.MS_PER_DAY;
|
|
173
173
|
if (newTick < 0) newTick += Time.MS_PER_DAY;
|
|
174
174
|
return new Time(newTick);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
/**
|
|
177
|
+
/** Return new instance with specified minutes added (24-hour wraparound) */
|
|
178
178
|
addMinutes(minutes: number): Time {
|
|
179
179
|
let newTick = (this._tick + minutes * 60 * 1000) % Time.MS_PER_DAY;
|
|
180
180
|
if (newTick < 0) newTick += Time.MS_PER_DAY;
|
|
181
181
|
return new Time(newTick);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
184
|
+
/** Return new instance with specified seconds added (24-hour wraparound) */
|
|
185
185
|
addSeconds(seconds: number): Time {
|
|
186
186
|
let newTick = (this._tick + seconds * 1000) % Time.MS_PER_DAY;
|
|
187
187
|
if (newTick < 0) newTick += Time.MS_PER_DAY;
|
|
188
188
|
return new Time(newTick);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
/**
|
|
191
|
+
/** Return new instance with specified milliseconds added (24-hour wraparound) */
|
|
192
192
|
addMilliseconds(milliseconds: number): Time {
|
|
193
193
|
let newTick = (this._tick + milliseconds) % Time.MS_PER_DAY;
|
|
194
194
|
if (newTick < 0) newTick += Time.MS_PER_DAY;
|
|
@@ -197,12 +197,12 @@ export class Time {
|
|
|
197
197
|
|
|
198
198
|
//#endregion
|
|
199
199
|
|
|
200
|
-
//#region
|
|
200
|
+
//#region Formatting
|
|
201
201
|
|
|
202
202
|
/**
|
|
203
|
-
*
|
|
204
|
-
* @param format
|
|
205
|
-
* @see dtFormat
|
|
203
|
+
* Convert to string with specified format
|
|
204
|
+
* @param format Format string
|
|
205
|
+
* @see dtFormat for supported format strings
|
|
206
206
|
*/
|
|
207
207
|
toFormatString(formatStr: string): string {
|
|
208
208
|
return formatDate(formatStr, {
|
package/src/types/uuid.ts
CHANGED
|
@@ -2,16 +2,16 @@ import type { Bytes } from "../common.types";
|
|
|
2
2
|
import { ArgumentError } from "../errors/argument-error";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* UUID v4
|
|
5
|
+
* UUID v4 class
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Generates cryptographically secure UUIDs based on crypto.getRandomValues. (Chrome 79+, Node.js compatible)
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* const id = Uuid.new();
|
|
11
11
|
* const fromStr = new Uuid("550e8400-e29b-41d4-a716-446655440000");
|
|
12
12
|
*/
|
|
13
13
|
export class Uuid {
|
|
14
|
-
//
|
|
14
|
+
// Pre-calculate hex strings for 0x00 ~ 0xFF (256 entries)
|
|
15
15
|
private static readonly _hexTable: string[] = Array.from({ length: 256 }, (_, i) =>
|
|
16
16
|
i.toString(16).padStart(2, "0"),
|
|
17
17
|
);
|
|
@@ -19,7 +19,7 @@ export class Uuid {
|
|
|
19
19
|
private static readonly _uuidRegex =
|
|
20
20
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21
21
|
|
|
22
|
-
/** 16
|
|
22
|
+
/** Convert 16-byte array to UUID string */
|
|
23
23
|
private static _bytesToUuidStr(bytes: Uint8Array): string {
|
|
24
24
|
const h = Uuid._hexTable;
|
|
25
25
|
return (
|
|
@@ -46,12 +46,12 @@ export class Uuid {
|
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
49
|
+
/** Create new UUID v4 instance */
|
|
50
50
|
static new(): Uuid {
|
|
51
51
|
const bytes = new Uint8Array(16);
|
|
52
52
|
crypto.getRandomValues(bytes);
|
|
53
53
|
|
|
54
|
-
// UUID v4
|
|
54
|
+
// Set UUID v4 bits
|
|
55
55
|
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
56
56
|
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
57
57
|
|
|
@@ -59,13 +59,13 @@ export class Uuid {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* 16
|
|
63
|
-
* @param bytes 16
|
|
64
|
-
* @throws {ArgumentError}
|
|
62
|
+
* Create UUID from 16-byte Uint8Array
|
|
63
|
+
* @param bytes 16-byte array
|
|
64
|
+
* @throws {ArgumentError} If byte size is not 16
|
|
65
65
|
*/
|
|
66
66
|
static fromBytes(bytes: Bytes): Uuid {
|
|
67
67
|
if (bytes.length !== 16) {
|
|
68
|
-
throw new ArgumentError("UUID
|
|
68
|
+
throw new ArgumentError("UUID byte size must be 16.", { length: bytes.length });
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
return new Uuid(Uuid._bytesToUuidStr(bytes));
|
|
@@ -74,26 +74,26 @@ export class Uuid {
|
|
|
74
74
|
private readonly _uuid: string;
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
* @param uuid UUID
|
|
78
|
-
* @throws {ArgumentError}
|
|
77
|
+
* @param uuid UUID string (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
78
|
+
* @throws {ArgumentError} If format is invalid
|
|
79
79
|
*/
|
|
80
80
|
constructor(uuid: string) {
|
|
81
81
|
if (!Uuid._uuidRegex.test(uuid)) {
|
|
82
|
-
throw new ArgumentError("UUID
|
|
82
|
+
throw new ArgumentError("Invalid UUID format.", { uuid });
|
|
83
83
|
}
|
|
84
84
|
this._uuid = uuid;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/** UUID
|
|
87
|
+
/** Convert UUID to string */
|
|
88
88
|
toString(): string {
|
|
89
89
|
return this._uuid;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/** UUID
|
|
92
|
+
/** Convert UUID to 16-byte Uint8Array */
|
|
93
93
|
toBytes(): Bytes {
|
|
94
94
|
const u = this._uuid;
|
|
95
|
-
// UUID
|
|
96
|
-
//
|
|
95
|
+
// UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)
|
|
96
|
+
// Hyphen positions: 8, 13, 18, 23
|
|
97
97
|
return new Uint8Array([
|
|
98
98
|
Number.parseInt(u.substring(0, 2), 16),
|
|
99
99
|
Number.parseInt(u.substring(2, 4), 16),
|