@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.
- package/.cache/typecheck-browser.tsbuildinfo +1 -0
- package/.cache/typecheck-node.tsbuildinfo +1 -0
- package/.cache/typecheck-tests-browser.tsbuildinfo +1 -0
- package/.cache/typecheck-tests-node.tsbuildinfo +1 -0
- package/README.md +887 -0
- package/dist/common.types.d.ts +74 -0
- package/dist/common.types.d.ts.map +1 -0
- package/dist/common.types.js +5 -0
- package/dist/common.types.js.map +7 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +9 -0
- package/dist/env.js.map +7 -0
- package/dist/errors/argument-error.d.ts +25 -0
- package/dist/errors/argument-error.d.ts.map +1 -0
- package/dist/errors/argument-error.js +18 -0
- package/dist/errors/argument-error.js.map +7 -0
- package/dist/errors/not-implemented-error.d.ts +29 -0
- package/dist/errors/not-implemented-error.d.ts.map +1 -0
- package/dist/errors/not-implemented-error.js +14 -0
- package/dist/errors/not-implemented-error.js.map +7 -0
- package/dist/errors/sd-error.d.ts +27 -0
- package/dist/errors/sd-error.d.ts.map +1 -0
- package/dist/errors/sd-error.js +23 -0
- package/dist/errors/sd-error.js.map +7 -0
- package/dist/errors/timeout-error.d.ts +31 -0
- package/dist/errors/timeout-error.d.ts.map +1 -0
- package/dist/errors/timeout-error.js +17 -0
- package/dist/errors/timeout-error.js.map +7 -0
- package/dist/extensions/arr-ext.d.ts +15 -0
- package/dist/extensions/arr-ext.d.ts.map +1 -0
- package/dist/extensions/arr-ext.helpers.d.ts +19 -0
- package/dist/extensions/arr-ext.helpers.d.ts.map +1 -0
- package/dist/extensions/arr-ext.helpers.js +35 -0
- package/dist/extensions/arr-ext.helpers.js.map +7 -0
- package/dist/extensions/arr-ext.js +546 -0
- package/dist/extensions/arr-ext.js.map +7 -0
- package/dist/extensions/arr-ext.types.d.ts +215 -0
- package/dist/extensions/arr-ext.types.d.ts.map +1 -0
- package/dist/extensions/arr-ext.types.js +1 -0
- package/dist/extensions/arr-ext.types.js.map +7 -0
- package/dist/extensions/map-ext.d.ts +57 -0
- package/dist/extensions/map-ext.d.ts.map +1 -0
- package/dist/extensions/map-ext.js +26 -0
- package/dist/extensions/map-ext.js.map +7 -0
- package/dist/extensions/set-ext.d.ts +36 -0
- package/dist/extensions/set-ext.d.ts.map +1 -0
- package/dist/extensions/set-ext.js +29 -0
- package/dist/extensions/set-ext.js.map +7 -0
- package/dist/features/debounce-queue.d.ts +53 -0
- package/dist/features/debounce-queue.d.ts.map +1 -0
- package/dist/features/debounce-queue.js +80 -0
- package/dist/features/debounce-queue.js.map +7 -0
- package/dist/features/event-emitter.d.ts +66 -0
- package/dist/features/event-emitter.d.ts.map +1 -0
- package/dist/features/event-emitter.js +82 -0
- package/dist/features/event-emitter.js.map +7 -0
- package/dist/features/serial-queue.d.ts +47 -0
- package/dist/features/serial-queue.d.ts.map +1 -0
- package/dist/features/serial-queue.js +66 -0
- package/dist/features/serial-queue.js.map +7 -0
- package/dist/globals.d.ts +12 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +1 -0
- package/dist/globals.js.map +7 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +7 -0
- package/dist/types/date-only.d.ts +152 -0
- package/dist/types/date-only.d.ts.map +1 -0
- package/dist/types/date-only.js +251 -0
- package/dist/types/date-only.js.map +7 -0
- package/dist/types/date-time.d.ts +96 -0
- package/dist/types/date-time.d.ts.map +1 -0
- package/dist/types/date-time.js +220 -0
- package/dist/types/date-time.js.map +7 -0
- package/dist/types/lazy-gc-map.d.ts +80 -0
- package/dist/types/lazy-gc-map.d.ts.map +1 -0
- package/dist/types/lazy-gc-map.js +179 -0
- package/dist/types/lazy-gc-map.js.map +7 -0
- package/dist/types/time.d.ts +68 -0
- package/dist/types/time.d.ts.map +1 -0
- package/dist/types/time.js +151 -0
- package/dist/types/time.js.map +7 -0
- package/dist/types/uuid.d.ts +35 -0
- package/dist/types/uuid.d.ts.map +1 -0
- package/dist/types/uuid.js +71 -0
- package/dist/types/uuid.js.map +7 -0
- package/dist/utils/bytes.d.ts +51 -0
- package/dist/utils/bytes.d.ts.map +1 -0
- package/dist/utils/bytes.js +89 -0
- package/dist/utils/bytes.js.map +7 -0
- package/dist/utils/date-format.d.ts +90 -0
- package/dist/utils/date-format.d.ts.map +1 -0
- package/dist/utils/date-format.js +106 -0
- package/dist/utils/date-format.js.map +7 -0
- package/dist/utils/json.d.ts +34 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +152 -0
- package/dist/utils/json.js.map +7 -0
- package/dist/utils/num.d.ts +60 -0
- package/dist/utils/num.d.ts.map +1 -0
- package/dist/utils/num.js +39 -0
- package/dist/utils/num.js.map +7 -0
- package/dist/utils/obj.d.ts +258 -0
- package/dist/utils/obj.d.ts.map +1 -0
- package/dist/utils/obj.js +538 -0
- package/dist/utils/obj.js.map +7 -0
- package/dist/utils/path.d.ts +23 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +21 -0
- package/dist/utils/path.js.map +7 -0
- package/dist/utils/primitive.d.ts +18 -0
- package/dist/utils/primitive.d.ts.map +1 -0
- package/dist/utils/primitive.js +20 -0
- package/dist/utils/primitive.js.map +7 -0
- package/dist/utils/str.d.ts +103 -0
- package/dist/utils/str.d.ts.map +1 -0
- package/dist/utils/str.js +128 -0
- package/dist/utils/str.js.map +7 -0
- package/dist/utils/template-strings.d.ts +84 -0
- package/dist/utils/template-strings.d.ts.map +1 -0
- package/dist/utils/template-strings.js +49 -0
- package/dist/utils/template-strings.js.map +7 -0
- package/dist/utils/transferable.d.ts +47 -0
- package/dist/utils/transferable.d.ts.map +1 -0
- package/dist/utils/transferable.js +153 -0
- package/dist/utils/transferable.js.map +7 -0
- package/dist/utils/wait.d.ts +19 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +19 -0
- package/dist/utils/wait.js.map +7 -0
- package/dist/utils/xml.d.ts +36 -0
- package/dist/utils/xml.d.ts.map +1 -0
- package/dist/utils/xml.js +51 -0
- package/dist/utils/xml.js.map +7 -0
- package/dist/zip/sd-zip.d.ts +80 -0
- package/dist/zip/sd-zip.d.ts.map +1 -0
- package/dist/zip/sd-zip.js +153 -0
- package/dist/zip/sd-zip.js.map +7 -0
- package/package.json +31 -0
- package/src/common.types.ts +91 -0
- package/src/env.ts +11 -0
- package/src/errors/argument-error.ts +40 -0
- package/src/errors/not-implemented-error.ts +32 -0
- package/src/errors/sd-error.ts +53 -0
- package/src/errors/timeout-error.ts +36 -0
- package/src/extensions/arr-ext.helpers.ts +53 -0
- package/src/extensions/arr-ext.ts +777 -0
- package/src/extensions/arr-ext.types.ts +258 -0
- package/src/extensions/map-ext.ts +86 -0
- package/src/extensions/set-ext.ts +68 -0
- package/src/features/debounce-queue.ts +116 -0
- package/src/features/event-emitter.ts +112 -0
- package/src/features/serial-queue.ts +94 -0
- package/src/globals.ts +12 -0
- package/src/index.ts +55 -0
- package/src/types/date-only.ts +329 -0
- package/src/types/date-time.ts +294 -0
- package/src/types/lazy-gc-map.ts +244 -0
- package/src/types/time.ts +210 -0
- package/src/types/uuid.ts +113 -0
- package/src/utils/bytes.ts +160 -0
- package/src/utils/date-format.ts +239 -0
- package/src/utils/json.ts +230 -0
- package/src/utils/num.ts +97 -0
- package/src/utils/obj.ts +956 -0
- package/src/utils/path.ts +40 -0
- package/src/utils/primitive.ts +33 -0
- package/src/utils/str.ts +252 -0
- package/src/utils/template-strings.ts +132 -0
- package/src/utils/transferable.ts +269 -0
- package/src/utils/wait.ts +40 -0
- package/src/utils/xml.ts +105 -0
- package/src/zip/sd-zip.ts +218 -0
- package/tests/errors/errors.spec.ts +196 -0
- package/tests/extensions/array-extension.spec.ts +790 -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 +636 -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/types.spec.ts +55 -0
- package/tests/types/uuid.spec.ts +91 -0
- package/tests/utils/bytes-utils.spec.ts +230 -0
- package/tests/utils/date-format.spec.ts +371 -0
- package/tests/utils/debounce-queue.spec.ts +272 -0
- package/tests/utils/json.spec.ts +475 -0
- package/tests/utils/number.spec.ts +184 -0
- package/tests/utils/object.spec.ts +827 -0
- package/tests/utils/path.spec.ts +78 -0
- package/tests/utils/primitive.spec.ts +55 -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 +294 -0
- package/tests/utils/template-strings.spec.ts +96 -0
- package/tests/utils/transferable.spec.ts +698 -0
- package/tests/utils/wait.spec.ts +145 -0
- package/tests/utils/xml.spec.ts +146 -0
- package/tests/zip/sd-zip.spec.ts +234 -0
package/README.md
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
# @simplysm/core-common
|
|
2
|
+
|
|
3
|
+
A common utility package for the Simplysm framework.
|
|
4
|
+
As a neutral base module usable in both Node.js and browser environments, it provides date/time types, error classes, object/array/string utilities, JSON serialization, ZIP processing, prototype extensions, and more.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @simplysm/core-common
|
|
10
|
+
# or
|
|
11
|
+
pnpm add @simplysm/core-common
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Initialization
|
|
15
|
+
|
|
16
|
+
Import the package at your application entry point (e.g., `index.ts`, `main.ts`):
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import "@simplysm/core-common";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This import globally activates Array, Map, and Set prototype extensions.
|
|
23
|
+
To use extension methods (`getOrCreate()`, `toggle()`, etc.), you must import this at app startup.
|
|
24
|
+
|
|
25
|
+
## Main Modules
|
|
26
|
+
|
|
27
|
+
### Errors
|
|
28
|
+
|
|
29
|
+
Custom error classes. All are based on `SdError` and support cause chaining.
|
|
30
|
+
|
|
31
|
+
| Class | Description |
|
|
32
|
+
|--------|------|
|
|
33
|
+
| `SdError` | Base error class (error tracking with cause chain, automatic nested stack integration) |
|
|
34
|
+
| `ArgumentError` | Argument validation error (YAML formatting) |
|
|
35
|
+
| `NotImplementedError` | Indicates unimplemented functionality |
|
|
36
|
+
| `TimeoutError` | Timeout error |
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { SdError, ArgumentError, NotImplementedError, TimeoutError } from "@simplysm/core-common";
|
|
40
|
+
|
|
41
|
+
// SdError: track errors with cause chain
|
|
42
|
+
try {
|
|
43
|
+
await fetch(url);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
throw new SdError(err, "API call failed", "Failed to load user");
|
|
46
|
+
// Result message: "Failed to load user => API call failed => original error message"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ArgumentError: output argument object in YAML format
|
|
50
|
+
throw new ArgumentError("Invalid user", { userId: 123 });
|
|
51
|
+
// Result message: "Invalid user\n\nuserId: 123"
|
|
52
|
+
|
|
53
|
+
// NotImplementedError: indicate unimplemented branch
|
|
54
|
+
switch (type) {
|
|
55
|
+
case "A": return handleA();
|
|
56
|
+
case "B": throw new NotImplementedError(`Handling type ${type}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// TimeoutError: wait time exceeded
|
|
60
|
+
throw new TimeoutError(5, "API response wait exceeded");
|
|
61
|
+
// Result message: "Wait time exceeded(5): API response wait exceeded"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Types
|
|
65
|
+
|
|
66
|
+
Immutable custom type classes. All transformation methods return new instances.
|
|
67
|
+
|
|
68
|
+
| Class | Description |
|
|
69
|
+
|--------|------|
|
|
70
|
+
| `DateTime` | Date + time (millisecond precision, local timezone) |
|
|
71
|
+
| `DateOnly` | Date only (no time) |
|
|
72
|
+
| `Time` | Time only (no date, 24-hour cycle) |
|
|
73
|
+
| `Uuid` | UUID v4 (based on `crypto.getRandomValues`) |
|
|
74
|
+
| `LazyGcMap` | Map with auto-expiration (LRU style) |
|
|
75
|
+
|
|
76
|
+
#### DateTime
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { DateTime } from "@simplysm/core-common";
|
|
80
|
+
|
|
81
|
+
// Creation
|
|
82
|
+
const now = new DateTime(); // Current time
|
|
83
|
+
const dt = new DateTime(2025, 1, 15, 10, 30, 0); // Year, month, day, hour, minute, second
|
|
84
|
+
const fromTick = new DateTime(1705312200000); // Tick (milliseconds)
|
|
85
|
+
const fromDate = new DateTime(new Date()); // Date object
|
|
86
|
+
|
|
87
|
+
// Parsing
|
|
88
|
+
DateTime.parse("2025-01-15 10:30:00"); // yyyy-MM-dd HH:mm:ss
|
|
89
|
+
DateTime.parse("2025-01-15 10:30:00.123"); // yyyy-MM-dd HH:mm:ss.fff
|
|
90
|
+
DateTime.parse("20250115103000"); // yyyyMMddHHmmss
|
|
91
|
+
DateTime.parse("2025-01-15 오전 10:30:00"); // Korean AM/PM
|
|
92
|
+
DateTime.parse("2025-01-15T10:30:00Z"); // ISO 8601
|
|
93
|
+
|
|
94
|
+
// Properties (read-only)
|
|
95
|
+
dt.year; // 2025
|
|
96
|
+
dt.month; // 1 (1-12)
|
|
97
|
+
dt.day; // 15
|
|
98
|
+
dt.hour; // 10
|
|
99
|
+
dt.minute; // 30
|
|
100
|
+
dt.second; // 0
|
|
101
|
+
dt.millisecond; // 0
|
|
102
|
+
dt.tick; // Millisecond timestamp
|
|
103
|
+
dt.dayOfWeek; // Day of week (Sun~Sat: 0~6)
|
|
104
|
+
dt.isValid; // Validity check
|
|
105
|
+
|
|
106
|
+
// Immutable transformations (return new instances)
|
|
107
|
+
dt.setYear(2026); // Change year
|
|
108
|
+
dt.setMonth(3); // Change month (day auto-adjusted)
|
|
109
|
+
dt.addDays(7); // 7 days later
|
|
110
|
+
dt.addHours(-2); // 2 hours ago
|
|
111
|
+
dt.addMonths(1); // 1 month later
|
|
112
|
+
|
|
113
|
+
// Formatting
|
|
114
|
+
dt.toFormatString("yyyy-MM-dd"); // "2025-01-15"
|
|
115
|
+
dt.toFormatString("yyyy년 M월 d일 (ddd)"); // "2025년 1월 15일 (수)"
|
|
116
|
+
dt.toFormatString("tt h:mm:ss"); // "오전 10:30:00"
|
|
117
|
+
dt.toString(); // "2025-01-15T10:30:00.000+09:00"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### DateOnly
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { DateOnly } from "@simplysm/core-common";
|
|
124
|
+
|
|
125
|
+
// Creation and parsing
|
|
126
|
+
const today = new DateOnly();
|
|
127
|
+
const d = new DateOnly(2025, 1, 15);
|
|
128
|
+
DateOnly.parse("2025-01-15"); // No timezone influence
|
|
129
|
+
DateOnly.parse("20250115"); // No timezone influence
|
|
130
|
+
|
|
131
|
+
// Immutable transformations
|
|
132
|
+
d.addDays(30);
|
|
133
|
+
d.addMonths(-1);
|
|
134
|
+
d.setMonth(2); // Jan 31 -> Feb 28 (auto-adjusted)
|
|
135
|
+
|
|
136
|
+
// Week calculation (ISO 8601 standard)
|
|
137
|
+
d.getWeekSeqOfYear(); // { year: 2025, weekSeq: 3 }
|
|
138
|
+
d.getWeekSeqOfMonth(); // { year: 2025, monthSeq: 1, weekSeq: 3 }
|
|
139
|
+
|
|
140
|
+
// US-style week (Sunday start, first week with 1+ days)
|
|
141
|
+
d.getWeekSeqOfYear(0, 1);
|
|
142
|
+
|
|
143
|
+
// Reverse calculate date from week
|
|
144
|
+
DateOnly.getDateByYearWeekSeq({ year: 2025, weekSeq: 2 }); // 2025-01-06 (Monday)
|
|
145
|
+
|
|
146
|
+
// Formatting
|
|
147
|
+
d.toFormatString("yyyy년 MM월 dd일"); // "2025년 01월 15일"
|
|
148
|
+
d.toString(); // "2025-01-15"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Time
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { Time } from "@simplysm/core-common";
|
|
155
|
+
|
|
156
|
+
// Creation and parsing
|
|
157
|
+
const now = new Time();
|
|
158
|
+
const t = new Time(14, 30, 0);
|
|
159
|
+
Time.parse("14:30:00"); // HH:mm:ss
|
|
160
|
+
Time.parse("14:30:00.123"); // HH:mm:ss.fff
|
|
161
|
+
Time.parse("오후 2:30:00"); // Korean AM/PM
|
|
162
|
+
|
|
163
|
+
// 24-hour cycle
|
|
164
|
+
t.addHours(12); // 14:30 + 12 hours = 02:30 (cycles, not next day)
|
|
165
|
+
t.addMinutes(-60); // 14:30 - 60 minutes = 13:30
|
|
166
|
+
|
|
167
|
+
// Formatting
|
|
168
|
+
t.toFormatString("tt h:mm"); // "오후 2:30"
|
|
169
|
+
t.toString(); // "14:30:00.000"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### Uuid
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { Uuid } from "@simplysm/core-common";
|
|
176
|
+
|
|
177
|
+
// Generate new UUID (cryptographically secure)
|
|
178
|
+
const id = Uuid.new();
|
|
179
|
+
|
|
180
|
+
// Create from string
|
|
181
|
+
const fromStr = new Uuid("550e8400-e29b-41d4-a716-446655440000");
|
|
182
|
+
|
|
183
|
+
// Byte conversion
|
|
184
|
+
const bytes = id.toBytes(); // Uint8Array (16 bytes)
|
|
185
|
+
const fromBytes = Uuid.fromBytes(bytes);
|
|
186
|
+
|
|
187
|
+
id.toString(); // "550e8400-e29b-41d4-a716-446655440000"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### LazyGcMap
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { LazyGcMap } from "@simplysm/core-common";
|
|
194
|
+
|
|
195
|
+
// using statement (recommended)
|
|
196
|
+
using map = new LazyGcMap<string, object>({
|
|
197
|
+
gcInterval: 10000, // GC execution interval: 10 seconds
|
|
198
|
+
expireTime: 60000, // Item expiration time: 60 seconds
|
|
199
|
+
onExpire: (key, value) => {
|
|
200
|
+
console.log(`Expired: ${key}`);
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
map.set("key1", { data: "hello" });
|
|
205
|
+
map.get("key1"); // Refreshes access time (LRU)
|
|
206
|
+
map.getOrCreate("key2", () => ({})); // Create and return if not exists
|
|
207
|
+
map.has("key1"); // Does not refresh access time
|
|
208
|
+
map.delete("key1");
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Features
|
|
212
|
+
|
|
213
|
+
Async operation control and event handling classes. All support `using` statements or `dispose()`.
|
|
214
|
+
|
|
215
|
+
| Class | Description |
|
|
216
|
+
|--------|------|
|
|
217
|
+
| `DebounceQueue` | Async debounce queue (executes only last request) |
|
|
218
|
+
| `SerialQueue` | Async serial queue (sequential execution) |
|
|
219
|
+
| `EventEmitter` | EventTarget wrapper (type-safe events) |
|
|
220
|
+
|
|
221
|
+
#### DebounceQueue
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { DebounceQueue } from "@simplysm/core-common";
|
|
225
|
+
|
|
226
|
+
using queue = new DebounceQueue(300); // 300ms debounce
|
|
227
|
+
|
|
228
|
+
// Error handling
|
|
229
|
+
queue.on("error", (err) => console.error(err));
|
|
230
|
+
|
|
231
|
+
// Only last call is executed
|
|
232
|
+
queue.run(() => console.log("1")); // Ignored
|
|
233
|
+
queue.run(() => console.log("2")); // Ignored
|
|
234
|
+
queue.run(() => console.log("3")); // Executed after 300ms
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### SerialQueue
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { SerialQueue } from "@simplysm/core-common";
|
|
241
|
+
|
|
242
|
+
using queue = new SerialQueue(100); // 100ms interval between tasks
|
|
243
|
+
|
|
244
|
+
queue.on("error", (err) => console.error(err));
|
|
245
|
+
|
|
246
|
+
queue.run(async () => { await fetch("/api/1"); });
|
|
247
|
+
queue.run(async () => { await fetch("/api/2"); }); // Runs after #1 completes
|
|
248
|
+
queue.run(async () => { await fetch("/api/3"); }); // Runs after #2 completes
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### EventEmitter
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { EventEmitter } from "@simplysm/core-common";
|
|
255
|
+
|
|
256
|
+
interface MyEvents {
|
|
257
|
+
data: string;
|
|
258
|
+
error: Error;
|
|
259
|
+
done: void;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
class MyService extends EventEmitter<MyEvents> {
|
|
263
|
+
process(): void {
|
|
264
|
+
this.emit("data", "result data");
|
|
265
|
+
this.emit("done"); // void type called without arguments
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const service = new MyService();
|
|
270
|
+
service.on("data", (data) => console.log(data)); // data: string (type inferred)
|
|
271
|
+
service.off("data", listener); // Remove listener
|
|
272
|
+
service.listenerCount("data"); // Number of registered listeners
|
|
273
|
+
service.dispose(); // Remove all listeners
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Utils
|
|
277
|
+
|
|
278
|
+
Utility functions.
|
|
279
|
+
|
|
280
|
+
#### Object utilities (obj)
|
|
281
|
+
|
|
282
|
+
| Function | Description |
|
|
283
|
+
|------|------|
|
|
284
|
+
| `objClone` | Deep clone (supports circular references, custom types) |
|
|
285
|
+
| `objEqual` | Deep comparison (include/exclude keys, array order ignore option) |
|
|
286
|
+
| `objMerge` | Deep merge (source + target, array processing option) |
|
|
287
|
+
| `objMerge3` | 3-way merge (conflict detection) |
|
|
288
|
+
| `objOmit` | Exclude specific keys |
|
|
289
|
+
| `objPick` | Select specific keys |
|
|
290
|
+
| `objGetChainValue` | Query value by chain path (`"a.b[0].c"`) |
|
|
291
|
+
| `objSetChainValue` | Set value by chain path |
|
|
292
|
+
| `objDeleteChainValue` | Delete value by chain path |
|
|
293
|
+
| `objKeys` | Type-safe `Object.keys` |
|
|
294
|
+
| `objEntries` | Type-safe `Object.entries` |
|
|
295
|
+
| `objFromEntries` | Type-safe `Object.fromEntries` |
|
|
296
|
+
| `objMap` | Transform each entry of object and return new object |
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import {
|
|
300
|
+
objClone, objEqual, objMerge, objMerge3,
|
|
301
|
+
objOmit, objPick, objGetChainValue, objSetChainValue,
|
|
302
|
+
objKeys, objEntries, objMap,
|
|
303
|
+
} from "@simplysm/core-common";
|
|
304
|
+
|
|
305
|
+
// Deep clone (supports custom types like DateTime, Uuid)
|
|
306
|
+
const cloned = objClone({ date: new DateTime(), nested: { arr: [1, 2] } });
|
|
307
|
+
|
|
308
|
+
// Deep comparison
|
|
309
|
+
objEqual({ a: 1, b: [2] }, { a: 1, b: [2] }); // true
|
|
310
|
+
objEqual(arr1, arr2, { ignoreArrayIndex: true }); // Ignore array order
|
|
311
|
+
objEqual(obj1, obj2, { topLevelExcludes: ["updatedAt"] }); // Exclude specific keys
|
|
312
|
+
|
|
313
|
+
// Deep merge
|
|
314
|
+
objMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 } });
|
|
315
|
+
// { a: 1, b: { c: 2, d: 3 } }
|
|
316
|
+
|
|
317
|
+
// 3-way merge (conflict detection)
|
|
318
|
+
const { conflict, result } = objMerge3(
|
|
319
|
+
{ a: 1, b: 2 }, // source (change #1)
|
|
320
|
+
{ a: 1, b: 1 }, // origin (base)
|
|
321
|
+
{ a: 2, b: 1 }, // target (change #2)
|
|
322
|
+
);
|
|
323
|
+
// conflict: false, result: { a: 2, b: 2 }
|
|
324
|
+
|
|
325
|
+
// Key selection/exclusion
|
|
326
|
+
objOmit(user, ["password", "email"]);
|
|
327
|
+
objPick(user, ["name", "age"]);
|
|
328
|
+
|
|
329
|
+
// Chain path
|
|
330
|
+
objGetChainValue(obj, "a.b[0].c");
|
|
331
|
+
objSetChainValue(obj, "a.b[0].c", "value");
|
|
332
|
+
|
|
333
|
+
// Type-safe Object utilities
|
|
334
|
+
objKeys(obj); // (keyof typeof obj)[]
|
|
335
|
+
objEntries(obj); // [keyof typeof obj, typeof obj[keyof typeof obj]][]
|
|
336
|
+
objMap(colors, (key, rgb) => [null, `rgb(${rgb})`]); // Transform values only (keep keys)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### JSON utilities (json)
|
|
340
|
+
|
|
341
|
+
| Function | Description |
|
|
342
|
+
|------|------|
|
|
343
|
+
| `jsonStringify` | JSON serialization with custom type support |
|
|
344
|
+
| `jsonParse` | JSON deserialization with custom type restoration |
|
|
345
|
+
|
|
346
|
+
Serializes/restores `DateTime`, `DateOnly`, `Time`, `Uuid`, `Date`, `Set`, `Map`, `Error`, `Uint8Array` types using `__type__` metadata.
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import { jsonStringify, jsonParse, DateTime, Uuid } from "@simplysm/core-common";
|
|
350
|
+
|
|
351
|
+
const data = {
|
|
352
|
+
createdAt: new DateTime(2025, 1, 15),
|
|
353
|
+
id: Uuid.new(),
|
|
354
|
+
tags: new Set(["a", "b"]),
|
|
355
|
+
meta: new Map([["key", "value"]]),
|
|
356
|
+
file: new Uint8Array([1, 2, 3]),
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Serialization (preserves custom types)
|
|
360
|
+
const json = jsonStringify(data, { space: 2 });
|
|
361
|
+
|
|
362
|
+
// Deserialization (restores custom types)
|
|
363
|
+
const parsed = jsonParse(json);
|
|
364
|
+
// parsed.createdAt instanceof DateTime === true
|
|
365
|
+
// parsed.id instanceof Uuid === true
|
|
366
|
+
// parsed.tags instanceof Set === true
|
|
367
|
+
|
|
368
|
+
// For logging: hide binary data
|
|
369
|
+
jsonStringify(data, { redactBytes: true });
|
|
370
|
+
// Uint8Array content replaced with "__hidden__"
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
#### XML utilities (xml)
|
|
374
|
+
|
|
375
|
+
| Function | Description |
|
|
376
|
+
|------|------|
|
|
377
|
+
| `xmlParse` | Parse XML string to object (attributes: `$`, text: `_`) |
|
|
378
|
+
| `xmlStringify` | Serialize object to XML string |
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { xmlParse, xmlStringify } from "@simplysm/core-common";
|
|
382
|
+
|
|
383
|
+
const obj = xmlParse('<root id="1"><item>hello</item></root>');
|
|
384
|
+
// { root: { $: { id: "1" }, item: [{ _: "hello" }] } }
|
|
385
|
+
|
|
386
|
+
const xml = xmlStringify(obj);
|
|
387
|
+
// '<root id="1"><item>hello</item></root>'
|
|
388
|
+
|
|
389
|
+
// Remove namespace prefix
|
|
390
|
+
xmlParse('<ns:root><ns:item>text</ns:item></ns:root>', { stripTagPrefix: true });
|
|
391
|
+
// { root: { item: [{ _: "text" }] } }
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### String utilities (str)
|
|
395
|
+
|
|
396
|
+
| Function | Description |
|
|
397
|
+
|------|------|
|
|
398
|
+
| `strGetSuffix` | Korean postposition handling (을/를, 은/는, 이/가, 과/와, 이랑/랑, 으로/로, 이라/라) |
|
|
399
|
+
| `strReplaceFullWidth` | Convert full-width characters to half-width |
|
|
400
|
+
| `strToPascalCase` | PascalCase conversion |
|
|
401
|
+
| `strToCamelCase` | camelCase conversion |
|
|
402
|
+
| `strToKebabCase` | kebab-case conversion |
|
|
403
|
+
| `strToSnakeCase` | snake_case conversion |
|
|
404
|
+
| `strIsNullOrEmpty` | Check for undefined/null/empty string (type guard) |
|
|
405
|
+
| `strInsert` | Insert at specific position in string |
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import {
|
|
409
|
+
strGetSuffix, strToCamelCase, strToKebabCase,
|
|
410
|
+
strIsNullOrEmpty, strReplaceFullWidth,
|
|
411
|
+
} from "@simplysm/core-common";
|
|
412
|
+
|
|
413
|
+
// Korean postposition
|
|
414
|
+
strGetSuffix("사과", "을"); // "를"
|
|
415
|
+
strGetSuffix("책", "이"); // "이"
|
|
416
|
+
strGetSuffix("서울", "로"); // "로" (ㄹ final consonant uses "로")
|
|
417
|
+
|
|
418
|
+
// Case conversion
|
|
419
|
+
strToCamelCase("hello-world"); // "helloWorld"
|
|
420
|
+
strToKebabCase("HelloWorld"); // "hello-world"
|
|
421
|
+
|
|
422
|
+
// Empty string check (type guard)
|
|
423
|
+
if (strIsNullOrEmpty(name)) {
|
|
424
|
+
// name: "" | undefined
|
|
425
|
+
} else {
|
|
426
|
+
// name: string (non-empty string)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Full-width -> half-width
|
|
430
|
+
strReplaceFullWidth("A123(株)"); // "A123(株)"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
#### Number utilities (num)
|
|
434
|
+
|
|
435
|
+
| Function | Description |
|
|
436
|
+
|------|------|
|
|
437
|
+
| `numParseInt` | Parse string to integer (remove non-digit characters) |
|
|
438
|
+
| `numParseFloat` | Parse string to float |
|
|
439
|
+
| `numParseRoundedInt` | Round float and return integer |
|
|
440
|
+
| `numFormat` | Thousands separator formatting |
|
|
441
|
+
| `numIsNullOrEmpty` | Check for undefined/null/0 (type guard) |
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
import { numParseInt, numParseFloat, numFormat, numIsNullOrEmpty } from "@simplysm/core-common";
|
|
445
|
+
|
|
446
|
+
numParseInt("12,345원"); // 12345
|
|
447
|
+
numParseFloat("3.14%"); // 3.14
|
|
448
|
+
numFormat(1234567, { max: 2 }); // "1,234,567"
|
|
449
|
+
numFormat(1234, { min: 2, max: 2 }); // "1,234.00"
|
|
450
|
+
|
|
451
|
+
if (numIsNullOrEmpty(count)) {
|
|
452
|
+
// count: 0 | undefined
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### Date/time formatting (date-format)
|
|
457
|
+
|
|
458
|
+
| Function | Description |
|
|
459
|
+
|------|------|
|
|
460
|
+
| `formatDate` | Convert date/time to string according to format string |
|
|
461
|
+
| `normalizeMonth` | Normalize year/month/day when setting month |
|
|
462
|
+
|
|
463
|
+
Supports the same format strings as C#:
|
|
464
|
+
|
|
465
|
+
| Format | Description | Example |
|
|
466
|
+
|------|------|------|
|
|
467
|
+
| `yyyy` | 4-digit year | 2024 |
|
|
468
|
+
| `yy` | 2-digit year | 24 |
|
|
469
|
+
| `MM` | 0-padded month | 01~12 |
|
|
470
|
+
| `M` | Month | 1~12 |
|
|
471
|
+
| `ddd` | Day of week (Korean) | 일, 월, 화, 수, 목, 금, 토 |
|
|
472
|
+
| `dd` | 0-padded day | 01~31 |
|
|
473
|
+
| `d` | Day | 1~31 |
|
|
474
|
+
| `tt` | AM/PM | 오전, 오후 |
|
|
475
|
+
| `HH` | 0-padded 24-hour | 00~23 |
|
|
476
|
+
| `hh` | 0-padded 12-hour | 01~12 |
|
|
477
|
+
| `mm` | 0-padded minute | 00~59 |
|
|
478
|
+
| `ss` | 0-padded second | 00~59 |
|
|
479
|
+
| `fff` | Millisecond (3 digits) | 000~999 |
|
|
480
|
+
| `zzz` | Timezone offset | +09:00 |
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
import { formatDate, normalizeMonth } from "@simplysm/core-common";
|
|
484
|
+
|
|
485
|
+
formatDate("yyyy-MM-dd", { year: 2024, month: 3, day: 15 });
|
|
486
|
+
// "2024-03-15"
|
|
487
|
+
|
|
488
|
+
formatDate("yyyy년 M월 d일 (ddd)", { year: 2024, month: 3, day: 15 });
|
|
489
|
+
// "2024년 3월 15일 (금)"
|
|
490
|
+
|
|
491
|
+
normalizeMonth(2025, 13, 15); // { year: 2026, month: 1, day: 15 }
|
|
492
|
+
normalizeMonth(2025, 2, 31); // { year: 2025, month: 2, day: 28 }
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
#### Byte utilities (bytes)
|
|
496
|
+
|
|
497
|
+
| Function | Description |
|
|
498
|
+
|------|------|
|
|
499
|
+
| `bytesConcat` | Concatenate multiple Uint8Arrays |
|
|
500
|
+
| `bytesToHex` | Convert Uint8Array to hex string |
|
|
501
|
+
| `bytesFromHex` | Convert hex string to Uint8Array |
|
|
502
|
+
| `bytesToBase64` | Convert Uint8Array to base64 string |
|
|
503
|
+
| `bytesFromBase64` | Convert base64 string to Uint8Array |
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
import { bytesConcat, bytesToHex, bytesFromHex, bytesToBase64, bytesFromBase64 } from "@simplysm/core-common";
|
|
507
|
+
|
|
508
|
+
bytesConcat([new Uint8Array([1, 2]), new Uint8Array([3, 4])]);
|
|
509
|
+
// Uint8Array([1, 2, 3, 4])
|
|
510
|
+
|
|
511
|
+
bytesToHex(new Uint8Array([255, 0, 127])); // "ff007f"
|
|
512
|
+
bytesFromHex("ff007f"); // Uint8Array([255, 0, 127])
|
|
513
|
+
|
|
514
|
+
bytesToBase64(new Uint8Array([72, 101, 108, 108, 111])); // "SGVsbG8="
|
|
515
|
+
bytesFromBase64("SGVsbG8="); // Uint8Array([72, 101, 108, 108, 111])
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
#### Async wait (wait)
|
|
519
|
+
|
|
520
|
+
| Function | Description |
|
|
521
|
+
|------|------|
|
|
522
|
+
| `waitTime` | Wait for specified time |
|
|
523
|
+
| `waitUntil` | Wait until condition is true (max attempts limit) |
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
import { waitTime, waitUntil } from "@simplysm/core-common";
|
|
527
|
+
|
|
528
|
+
await waitTime(1000); // Wait 1 second
|
|
529
|
+
|
|
530
|
+
// Wait for condition (100ms interval, max 50 attempts = 5 seconds)
|
|
531
|
+
await waitUntil(() => isReady, 100, 50);
|
|
532
|
+
// Throws TimeoutError after 50 attempts
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
#### Worker data conversion (transferable)
|
|
536
|
+
|
|
537
|
+
| Function | Description |
|
|
538
|
+
|------|------|
|
|
539
|
+
| `transferableEncode` | Serialize custom types into Worker-transferable form |
|
|
540
|
+
| `transferableDecode` | Deserialize Worker-received data to custom types |
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
import { transferableEncode, transferableDecode } from "@simplysm/core-common";
|
|
544
|
+
|
|
545
|
+
// Send to Worker
|
|
546
|
+
const { result, transferList } = transferableEncode(data);
|
|
547
|
+
worker.postMessage(result, transferList);
|
|
548
|
+
|
|
549
|
+
// Receive from Worker
|
|
550
|
+
const decoded = transferableDecode(event.data);
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
#### Path utilities (path)
|
|
554
|
+
|
|
555
|
+
Replacement for Node.js `path` module. Supports POSIX-style paths (`/`) only.
|
|
556
|
+
|
|
557
|
+
| Function | Description |
|
|
558
|
+
|------|------|
|
|
559
|
+
| `pathJoin` | Combine paths (`path.join` replacement) |
|
|
560
|
+
| `pathBasename` | Extract filename (`path.basename` replacement) |
|
|
561
|
+
| `pathExtname` | Extract extension (`path.extname` replacement) |
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
import { pathJoin, pathBasename, pathExtname } from "@simplysm/core-common";
|
|
565
|
+
|
|
566
|
+
pathJoin("/home", "user", "file.txt"); // "/home/user/file.txt"
|
|
567
|
+
pathBasename("/home/user/file.txt"); // "file.txt"
|
|
568
|
+
pathBasename("file.txt", ".txt"); // "file"
|
|
569
|
+
pathExtname("file.txt"); // ".txt"
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
#### Template literal tags (template-strings)
|
|
573
|
+
|
|
574
|
+
Tag functions for IDE code highlighting. Actual behavior is string combination + indentation cleanup.
|
|
575
|
+
|
|
576
|
+
| Function | Description |
|
|
577
|
+
|------|------|
|
|
578
|
+
| `js` | JavaScript code highlighting |
|
|
579
|
+
| `ts` | TypeScript code highlighting |
|
|
580
|
+
| `html` | HTML markup highlighting |
|
|
581
|
+
| `tsql` | MSSQL T-SQL highlighting |
|
|
582
|
+
| `mysql` | MySQL SQL highlighting |
|
|
583
|
+
| `pgsql` | PostgreSQL SQL highlighting |
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
import { tsql } from "@simplysm/core-common";
|
|
587
|
+
|
|
588
|
+
const query = tsql`
|
|
589
|
+
SELECT TOP 10 *
|
|
590
|
+
FROM Users
|
|
591
|
+
WHERE Name LIKE '%${keyword}%'
|
|
592
|
+
`;
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
#### Other utilities
|
|
596
|
+
|
|
597
|
+
| Function/Type | Description |
|
|
598
|
+
|-----------|------|
|
|
599
|
+
| `getPrimitiveTypeStr` | Infer `PrimitiveTypeStr` from runtime value |
|
|
600
|
+
| `env` | Environment variable object (`DEV`, `VER`, etc.) |
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
import { getPrimitiveTypeStr, env } from "@simplysm/core-common";
|
|
604
|
+
|
|
605
|
+
getPrimitiveTypeStr("hello"); // "string"
|
|
606
|
+
getPrimitiveTypeStr(123); // "number"
|
|
607
|
+
getPrimitiveTypeStr(new DateTime()); // "DateTime"
|
|
608
|
+
|
|
609
|
+
if (env.DEV) {
|
|
610
|
+
console.log("Development mode");
|
|
611
|
+
}
|
|
612
|
+
console.log(`Version: ${env.VER}`);
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Zip
|
|
616
|
+
|
|
617
|
+
ZIP file compression/decompression utility. Resources can be auto-cleaned with `await using`.
|
|
618
|
+
|
|
619
|
+
| Class | Description |
|
|
620
|
+
|--------|------|
|
|
621
|
+
| `ZipArchive` | ZIP file read/write/compress/extract |
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
import { ZipArchive } from "@simplysm/core-common";
|
|
625
|
+
|
|
626
|
+
// Read ZIP file
|
|
627
|
+
await using archive = new ZipArchive(zipBytes);
|
|
628
|
+
const content = await archive.get("file.txt");
|
|
629
|
+
const exists = await archive.exists("data.json");
|
|
630
|
+
|
|
631
|
+
// Extract all (with progress)
|
|
632
|
+
const files = await archive.extractAll((progress) => {
|
|
633
|
+
console.log(`${progress.fileName}: ${progress.extractedSize}/${progress.totalSize}`);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// Create ZIP file
|
|
637
|
+
await using newArchive = new ZipArchive();
|
|
638
|
+
newArchive.write("file.txt", textBytes);
|
|
639
|
+
newArchive.write("data.json", jsonBytes);
|
|
640
|
+
const zipBytes = await newArchive.compress();
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Type Utilities
|
|
644
|
+
|
|
645
|
+
TypeScript utility types.
|
|
646
|
+
|
|
647
|
+
| Type | Description |
|
|
648
|
+
|------|------|
|
|
649
|
+
| `Bytes` | Alias for `Uint8Array` (`Buffer` replacement) |
|
|
650
|
+
| `PrimitiveTypeStr` | Primitive type string keys (`"string"`, `"number"`, `"boolean"`, `"DateTime"`, `"DateOnly"`, `"Time"`, `"Uuid"`, `"Bytes"`) |
|
|
651
|
+
| `PrimitiveTypeMap` | Mapping from `PrimitiveTypeStr` to actual type |
|
|
652
|
+
| `PrimitiveType` | Union of all Primitive types |
|
|
653
|
+
| `DeepPartial<T>` | Recursively convert all properties to optional |
|
|
654
|
+
| `Type<T>` | Constructor type (for dependency injection, factory patterns) |
|
|
655
|
+
| `ObjUndefToOptional<T>` | Convert properties with `undefined` to optional |
|
|
656
|
+
| `ObjOptionalToUndef<T>` | Convert optional properties to `required + undefined` union |
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
import type { DeepPartial, Type, Bytes } from "@simplysm/core-common";
|
|
660
|
+
|
|
661
|
+
// DeepPartial: deep Partial
|
|
662
|
+
interface Config {
|
|
663
|
+
db: { host: string; port: number };
|
|
664
|
+
}
|
|
665
|
+
const partial: DeepPartial<Config> = { db: { host: "localhost" } };
|
|
666
|
+
|
|
667
|
+
// Type: constructor type
|
|
668
|
+
function create<T>(ctor: Type<T>): T {
|
|
669
|
+
return new ctor();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Bytes: Buffer replacement
|
|
673
|
+
const data: Bytes = new Uint8Array([1, 2, 3]);
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### Extensions
|
|
677
|
+
|
|
678
|
+
Array, Map, Set prototype extensions. Activated by `import "@simplysm/core-common"`.
|
|
679
|
+
|
|
680
|
+
#### Array extension methods
|
|
681
|
+
|
|
682
|
+
**Query**:
|
|
683
|
+
|
|
684
|
+
| Method | Description |
|
|
685
|
+
|--------|------|
|
|
686
|
+
| `single(predicate?)` | Return single element (error if 2+) |
|
|
687
|
+
| `first(predicate?)` | Return first element |
|
|
688
|
+
| `last(predicate?)` | Return last element |
|
|
689
|
+
|
|
690
|
+
**Filtering**:
|
|
691
|
+
|
|
692
|
+
| Method | Description |
|
|
693
|
+
|--------|------|
|
|
694
|
+
| `filterExists()` | Remove `null`/`undefined` |
|
|
695
|
+
| `ofType(type)` | Filter by type (`PrimitiveTypeStr` or constructor) |
|
|
696
|
+
| `filterAsync(predicate)` | Async filter |
|
|
697
|
+
|
|
698
|
+
**Mapping/Transformation**:
|
|
699
|
+
|
|
700
|
+
| Method | Description |
|
|
701
|
+
|--------|------|
|
|
702
|
+
| `mapAsync(selector)` | Async mapping (sequential execution) |
|
|
703
|
+
| `mapMany(selector?)` | flat + filterExists |
|
|
704
|
+
| `mapManyAsync(selector?)` | Async mapMany |
|
|
705
|
+
| `parallelAsync(fn)` | Parallel async mapping (`Promise.all`) |
|
|
706
|
+
|
|
707
|
+
**Grouping/Transformation**:
|
|
708
|
+
|
|
709
|
+
| Method | Description |
|
|
710
|
+
|--------|------|
|
|
711
|
+
| `groupBy(keySelector, valueSelector?)` | Group by key |
|
|
712
|
+
| `toMap(keySelector, valueSelector?)` | Convert to Map (error on duplicate key) |
|
|
713
|
+
| `toMapAsync(keySelector, valueSelector?)` | Async Map conversion |
|
|
714
|
+
| `toArrayMap(keySelector, valueSelector?)` | Convert to `Map<K, V[]>` |
|
|
715
|
+
| `toSetMap(keySelector, valueSelector?)` | Convert to `Map<K, Set<V>>` |
|
|
716
|
+
| `toMapValues(keySelector, valueSelector)` | Aggregate Map by group |
|
|
717
|
+
| `toObject(keySelector, valueSelector?)` | Convert to `Record<string, V>` |
|
|
718
|
+
| `toTree(key, parentKey)` | Convert to tree structure |
|
|
719
|
+
|
|
720
|
+
**Deduplication**:
|
|
721
|
+
|
|
722
|
+
| Method | Description |
|
|
723
|
+
|--------|------|
|
|
724
|
+
| `distinct(options?)` | Remove duplicates (return new array) |
|
|
725
|
+
| `distinctThis(options?)` | Remove duplicates (modify original) |
|
|
726
|
+
|
|
727
|
+
**Sorting**:
|
|
728
|
+
|
|
729
|
+
| Method | Description |
|
|
730
|
+
|--------|------|
|
|
731
|
+
| `orderBy(selector?)` | Ascending sort (return new array) |
|
|
732
|
+
| `orderByDesc(selector?)` | Descending sort (return new array) |
|
|
733
|
+
| `orderByThis(selector?)` | Ascending sort (modify original) |
|
|
734
|
+
| `orderByDescThis(selector?)` | Descending sort (modify original) |
|
|
735
|
+
|
|
736
|
+
**Comparison/Merging**:
|
|
737
|
+
|
|
738
|
+
| Method | Description |
|
|
739
|
+
|--------|------|
|
|
740
|
+
| `diffs(target, options?)` | Compare differences between two arrays |
|
|
741
|
+
| `oneWayDiffs(orgItems, keyProp, options?)` | One-way diff comparison (create/update/same) |
|
|
742
|
+
| `merge(target, options?)` | Merge arrays |
|
|
743
|
+
|
|
744
|
+
**Aggregation**:
|
|
745
|
+
|
|
746
|
+
| Method | Description |
|
|
747
|
+
|--------|------|
|
|
748
|
+
| `sum(selector?)` | Sum |
|
|
749
|
+
| `min(selector?)` | Minimum |
|
|
750
|
+
| `max(selector?)` | Maximum |
|
|
751
|
+
|
|
752
|
+
**Mutation (modify original)**:
|
|
753
|
+
|
|
754
|
+
| Method | Description |
|
|
755
|
+
|--------|------|
|
|
756
|
+
| `insert(index, ...items)` | Insert at specific position |
|
|
757
|
+
| `remove(itemOrSelector)` | Remove item |
|
|
758
|
+
| `toggle(item)` | Remove if exists, add if not |
|
|
759
|
+
| `clear()` | Remove all items |
|
|
760
|
+
| `shuffle()` | Shuffle (return new array) |
|
|
761
|
+
|
|
762
|
+
```typescript
|
|
763
|
+
import "@simplysm/core-common";
|
|
764
|
+
|
|
765
|
+
const users = [
|
|
766
|
+
{ id: 1, name: "Alice", dept: "dev" },
|
|
767
|
+
{ id: 2, name: "Bob", dept: "dev" },
|
|
768
|
+
{ id: 3, name: "Charlie", dept: "hr" },
|
|
769
|
+
];
|
|
770
|
+
|
|
771
|
+
// Query
|
|
772
|
+
users.single((u) => u.id === 1); // { id: 1, ... }
|
|
773
|
+
users.first(); // { id: 1, ... }
|
|
774
|
+
users.last(); // { id: 3, ... }
|
|
775
|
+
|
|
776
|
+
// Grouping
|
|
777
|
+
users.groupBy((u) => u.dept);
|
|
778
|
+
// [{ key: "dev", values: [...] }, { key: "hr", values: [...] }]
|
|
779
|
+
|
|
780
|
+
// Map conversion
|
|
781
|
+
users.toMap((u) => u.id); // Map<number, User>
|
|
782
|
+
users.toArrayMap((u) => u.dept); // Map<string, User[]>
|
|
783
|
+
|
|
784
|
+
// Sorting
|
|
785
|
+
users.orderBy((u) => u.name);
|
|
786
|
+
users.orderByDesc((u) => u.id);
|
|
787
|
+
|
|
788
|
+
// Filtering
|
|
789
|
+
[1, null, 2, undefined, 3].filterExists(); // [1, 2, 3]
|
|
790
|
+
|
|
791
|
+
// Deduplication
|
|
792
|
+
[1, 2, 2, 3, 3].distinct(); // [1, 2, 3]
|
|
793
|
+
|
|
794
|
+
// Async mapping (sequential execution)
|
|
795
|
+
await ids.mapAsync(async (id) => await fetchUser(id));
|
|
796
|
+
|
|
797
|
+
// Parallel async mapping
|
|
798
|
+
await ids.parallelAsync(async (id) => await fetchUser(id));
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
#### Map extension methods
|
|
802
|
+
|
|
803
|
+
| Method | Description |
|
|
804
|
+
|--------|------|
|
|
805
|
+
| `getOrCreate(key, value)` | If key doesn't exist, set new value and return |
|
|
806
|
+
| `update(key, updateFn)` | Update value for key using function |
|
|
807
|
+
|
|
808
|
+
```typescript
|
|
809
|
+
const map = new Map<string, number[]>();
|
|
810
|
+
|
|
811
|
+
// Create and return if value doesn't exist
|
|
812
|
+
const arr = map.getOrCreate("key", []);
|
|
813
|
+
arr.push(1);
|
|
814
|
+
|
|
815
|
+
// Create with factory function (when computation is expensive)
|
|
816
|
+
map.getOrCreate("key2", () => expensiveComputation());
|
|
817
|
+
|
|
818
|
+
// Update value
|
|
819
|
+
const countMap = new Map<string, number>();
|
|
820
|
+
countMap.update("key", (v) => (v ?? 0) + 1); // Increment counter
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
#### Set extension methods
|
|
824
|
+
|
|
825
|
+
| Method | Description |
|
|
826
|
+
|--------|------|
|
|
827
|
+
| `adds(...values)` | Add multiple values at once |
|
|
828
|
+
| `toggle(value, addOrDel?)` | Toggle value (remove if exists, add if not) |
|
|
829
|
+
|
|
830
|
+
```typescript
|
|
831
|
+
const set = new Set<number>([1, 2, 3]);
|
|
832
|
+
|
|
833
|
+
set.adds(4, 5, 6); // {1, 2, 3, 4, 5, 6}
|
|
834
|
+
set.toggle(2); // 2 exists so remove -> {1, 3, 4, 5, 6}
|
|
835
|
+
set.toggle(7); // 7 doesn't exist so add -> {1, 3, 4, 5, 6, 7}
|
|
836
|
+
set.toggle(8, "add"); // Force add
|
|
837
|
+
set.toggle(1, "del"); // Force delete
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
## Caveats
|
|
841
|
+
|
|
842
|
+
### Prototype Extension Conflicts
|
|
843
|
+
|
|
844
|
+
This package extends Array, Map, and Set prototypes.
|
|
845
|
+
Conflicts may occur when used with other libraries that extend the same method names.
|
|
846
|
+
In case of conflict, the last defined implementation is applied based on load order.
|
|
847
|
+
|
|
848
|
+
### Timezone Handling
|
|
849
|
+
|
|
850
|
+
When using `DateOnly.parse()`, `DateTime.parse()`:
|
|
851
|
+
- `yyyy-MM-dd`, `yyyyMMdd` format: parse directly from string (no timezone influence)
|
|
852
|
+
- ISO 8601 format (`2024-01-15T00:00:00Z`): interpret as UTC then convert to local
|
|
853
|
+
|
|
854
|
+
When server and client timezones differ, actively use `yyyy-MM-dd` format.
|
|
855
|
+
|
|
856
|
+
### Memory Management (LazyGcMap)
|
|
857
|
+
|
|
858
|
+
`LazyGcMap` has an internal GC timer, so it must be cleaned up.
|
|
859
|
+
|
|
860
|
+
```typescript
|
|
861
|
+
// using statement (recommended)
|
|
862
|
+
// gcInterval: GC execution interval (ms), expireTime: item expiration time (ms)
|
|
863
|
+
using map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 }); // GC every 10 seconds, expire after 60 seconds
|
|
864
|
+
|
|
865
|
+
// Or explicit dispose() call
|
|
866
|
+
const map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 }); // GC every 10 seconds, expire after 60 seconds
|
|
867
|
+
try {
|
|
868
|
+
// ... use
|
|
869
|
+
} finally {
|
|
870
|
+
map.dispose();
|
|
871
|
+
}
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
### jsonStringify's __type__ Reserved Word
|
|
875
|
+
|
|
876
|
+
`jsonStringify`/`jsonParse` uses objects with `__type__` and `data` keys for type restoration.
|
|
877
|
+
Be careful as user data in the form `{ __type__: "DateTime", data: "..." }` may be unintentionally type-converted.
|
|
878
|
+
|
|
879
|
+
### Circular References
|
|
880
|
+
|
|
881
|
+
- `objClone`: supports circular references (tracked with WeakMap)
|
|
882
|
+
- `jsonStringify`: throws TypeError on circular reference
|
|
883
|
+
- `transferableEncode`: throws TypeError on circular reference (includes path information)
|
|
884
|
+
|
|
885
|
+
## License
|
|
886
|
+
|
|
887
|
+
Apache-2.0
|