@simplysm/core-common 13.0.82 → 13.0.83
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 +35 -0
- package/docs/byte-utilities.md +55 -0
- package/docs/date-format-utilities.md +96 -0
- package/docs/environment.md +35 -0
- package/docs/error-utilities.md +35 -0
- package/docs/errors.md +79 -0
- package/docs/extensions.md +201 -0
- package/docs/features.md +88 -0
- package/docs/json-utilities.md +57 -0
- package/docs/number-utilities.md +76 -0
- package/docs/object-utilities.md +165 -0
- package/docs/path-utilities.md +54 -0
- package/docs/primitive-utilities.md +40 -0
- package/docs/string-utilities.md +79 -0
- package/docs/template-strings.md +105 -0
- package/docs/transfer-utilities.md +53 -0
- package/docs/types.md +242 -0
- package/docs/wait-utilities.md +50 -0
- package/docs/xml-utilities.md +48 -0
- package/docs/zip-archive.md +61 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @simplysm/core-common
|
|
2
|
+
|
|
3
|
+
> Simplysm package - Core module (common)
|
|
4
|
+
|
|
5
|
+
A platform-neutral (Node.js and browser) utility library providing immutable date/time types, rich Array/Map/Set extensions, typed error classes, object manipulation, serialization (JSON/XML/ZIP), and async control primitives. It serves as the leaf dependency for the entire Simplysm monorepo.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @simplysm/core-common
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Documentation
|
|
14
|
+
|
|
15
|
+
| Category | Description |
|
|
16
|
+
|----------|-------------|
|
|
17
|
+
| [Types](docs/types.md) | Immutable date/time classes (`DateTime`, `DateOnly`, `Time`), `Uuid`, `LazyGcMap`, and shared type aliases |
|
|
18
|
+
| [Errors](docs/errors.md) | Error class hierarchy: `SdError`, `ArgumentError`, `NotImplementedError`, `TimeoutError` |
|
|
19
|
+
| [Features](docs/features.md) | Async control primitives: `EventEmitter`, `DebounceQueue`, `SerialQueue` |
|
|
20
|
+
| [Extensions](docs/extensions.md) | Prototype extensions for `Array`, `Map`, and `Set` |
|
|
21
|
+
| [Object Utilities](docs/object-utilities.md) | `obj` namespace: `clone`, `equal`, `merge`, `merge3`, `omit`, `pick`, chain access, and type-safe Object helpers |
|
|
22
|
+
| [String Utilities](docs/string-utilities.md) | `str` namespace: case conversion, Korean particles, full-width conversion, and string helpers |
|
|
23
|
+
| [Number Utilities](docs/number-utilities.md) | `num` namespace: robust parsing (`parseInt`, `parseFloat`) and locale-aware formatting |
|
|
24
|
+
| [Byte Utilities](docs/byte-utilities.md) | `bytes` namespace: `concat`, hex/base64 encode/decode for `Uint8Array` |
|
|
25
|
+
| [Path Utilities](docs/path-utilities.md) | `path` namespace: browser-safe `join`, `basename`, `extname` (POSIX only) |
|
|
26
|
+
| [JSON Utilities](docs/json-utilities.md) | `json` namespace: `stringify` / `parse` with custom type support (`DateTime`, `Set`, `Map`, `Error`, etc.) |
|
|
27
|
+
| [XML Utilities](docs/xml-utilities.md) | `xml` namespace: `parse` / `stringify` via fast-xml-parser |
|
|
28
|
+
| [Wait Utilities](docs/wait-utilities.md) | `wait` namespace: `time` (sleep) and `until` (poll with timeout) |
|
|
29
|
+
| [Transfer Utilities](docs/transfer-utilities.md) | `transfer` namespace: Worker-safe `encode` / `decode` for custom types |
|
|
30
|
+
| [Date Format Utilities](docs/date-format-utilities.md) | `dt` namespace: C#-style format strings and month normalization |
|
|
31
|
+
| [Primitive Utilities](docs/primitive-utilities.md) | `primitive` namespace: runtime type-string inference |
|
|
32
|
+
| [Error Utilities](docs/error-utilities.md) | `err` namespace: extract message from unknown catch values |
|
|
33
|
+
| [Template Strings](docs/template-strings.md) | Tagged template literals (`js`, `ts`, `html`, `tsql`, `mysql`, `pgsql`) with auto-indent normalization |
|
|
34
|
+
| [ZIP Archive](docs/zip-archive.md) | `ZipArchive` class for reading, writing, and compressing ZIP files |
|
|
35
|
+
| [Environment](docs/environment.md) | `env` object exposing `DEV` and `VER` from `process.env` |
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Byte Utilities
|
|
2
|
+
|
|
3
|
+
Imported as the `bytes` namespace. Provides `Uint8Array` manipulation utilities.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { bytes } from "@simplysm/core-common";
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## concat
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
function concat(arrays: Bytes[]): Bytes;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Concatenates multiple `Uint8Array` instances into a single new array.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## toHex / fromHex
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
function toHex(bytes: Bytes): string;
|
|
23
|
+
function fromHex(hex: string): Bytes;
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Converts between `Uint8Array` and lowercase hex strings. `fromHex` throws `ArgumentError` for odd-length strings or invalid hex characters.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## toBase64 / fromBase64
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
function toBase64(bytes: Bytes): string;
|
|
34
|
+
function fromBase64(base64: string): Bytes;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Converts between `Uint8Array` and base64 strings. `fromBase64` throws `ArgumentError` for invalid characters or length.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Usage Examples
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { bytes } from "@simplysm/core-common";
|
|
45
|
+
|
|
46
|
+
const a = new Uint8Array([1, 2]);
|
|
47
|
+
const b = new Uint8Array([3, 4]);
|
|
48
|
+
bytes.concat([a, b]); // Uint8Array [1, 2, 3, 4]
|
|
49
|
+
|
|
50
|
+
bytes.toHex(new Uint8Array([255, 0, 127])); // "ff007f"
|
|
51
|
+
bytes.fromHex("ff007f"); // Uint8Array [255, 0, 127]
|
|
52
|
+
|
|
53
|
+
bytes.toBase64(new Uint8Array([72, 101, 108, 108, 111])); // "SGVsbG8="
|
|
54
|
+
bytes.fromBase64("SGVsbG8="); // Uint8Array [72, 101, 108, 108, 111]
|
|
55
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Date Format Utilities
|
|
2
|
+
|
|
3
|
+
Imported as the `dt` namespace. C#-style date/time formatting and month normalization.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { dt } from "@simplysm/core-common";
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## format
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
function format(formatString: string, args: {
|
|
13
|
+
year?: number;
|
|
14
|
+
month?: number;
|
|
15
|
+
day?: number;
|
|
16
|
+
hour?: number;
|
|
17
|
+
minute?: number;
|
|
18
|
+
second?: number;
|
|
19
|
+
millisecond?: number;
|
|
20
|
+
timezoneOffsetMinutes?: number;
|
|
21
|
+
}): string;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Converts date/time components to a string using a C#-compatible format string.
|
|
25
|
+
|
|
26
|
+
| Format | Description | Example |
|
|
27
|
+
|--------|-------------|---------|
|
|
28
|
+
| `yyyy` | 4-digit year | 2024 |
|
|
29
|
+
| `yy` | 2-digit year | 24 |
|
|
30
|
+
| `MM` | Zero-padded month | 01-12 |
|
|
31
|
+
| `M` | Month | 1-12 |
|
|
32
|
+
| `ddd` | Day of week (Korean) | Sun, Mon, ... |
|
|
33
|
+
| `dd` | Zero-padded day | 01-31 |
|
|
34
|
+
| `d` | Day | 1-31 |
|
|
35
|
+
| `tt` | AM/PM | AM, PM |
|
|
36
|
+
| `hh` | Zero-padded 12-hour | 01-12 |
|
|
37
|
+
| `h` | 12-hour | 1-12 |
|
|
38
|
+
| `HH` | Zero-padded 24-hour | 00-23 |
|
|
39
|
+
| `H` | 24-hour | 0-23 |
|
|
40
|
+
| `mm` | Zero-padded minute | 00-59 |
|
|
41
|
+
| `m` | Minute | 0-59 |
|
|
42
|
+
| `ss` | Zero-padded second | 00-59 |
|
|
43
|
+
| `s` | Second | 0-59 |
|
|
44
|
+
| `fff` | Milliseconds (3 digits) | 000-999 |
|
|
45
|
+
| `ff` | Milliseconds (2 digits) | 00-99 |
|
|
46
|
+
| `f` | Milliseconds (1 digit) | 0-9 |
|
|
47
|
+
| `zzz` | Timezone offset | +09:00 |
|
|
48
|
+
| `zz` | Timezone offset | +09 |
|
|
49
|
+
| `z` | Timezone offset | +9 |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## normalizeMonth
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
function normalizeMonth(year: number, month: number, day: number): {
|
|
57
|
+
year: number;
|
|
58
|
+
month: number;
|
|
59
|
+
day: number;
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Normalizes year/month/day when month is outside 1-12 range. Adjusts day to last day of month if needed (e.g., Jan 31 + setMonth(2) = Feb 28).
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## convert12To24
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
function convert12To24(rawHour: number, isPM: boolean): number;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Converts 12-hour format to 24-hour format. `12 AM = 0`, `12 PM = 12`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Usage Examples
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { dt } from "@simplysm/core-common";
|
|
81
|
+
|
|
82
|
+
dt.format("yyyy-MM-dd", { year: 2024, month: 3, day: 15 });
|
|
83
|
+
// "2024-03-15"
|
|
84
|
+
|
|
85
|
+
dt.format("tt h:mm:ss", { hour: 14, minute: 30, second: 45 });
|
|
86
|
+
// "PM 2:30:45"
|
|
87
|
+
|
|
88
|
+
dt.normalizeMonth(2025, 13, 15);
|
|
89
|
+
// { year: 2026, month: 1, day: 15 }
|
|
90
|
+
|
|
91
|
+
dt.normalizeMonth(2025, 2, 31);
|
|
92
|
+
// { year: 2025, month: 2, day: 28 }
|
|
93
|
+
|
|
94
|
+
dt.convert12To24(12, false); // 0 (12 AM)
|
|
95
|
+
dt.convert12To24(12, true); // 12 (12 PM)
|
|
96
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Environment
|
|
2
|
+
|
|
3
|
+
The `env` object exposes build-time environment variables.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { env } from "@simplysm/core-common";
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## env
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const env: {
|
|
13
|
+
DEV: boolean;
|
|
14
|
+
VER?: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
};
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `DEV` - Whether running in development mode. Parsed from `process.env.DEV` (defaults to `false`).
|
|
20
|
+
- `VER` - Application version string from `process.env.VER`.
|
|
21
|
+
- Additional `process.env` properties are spread into the object.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Usage Examples
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { env } from "@simplysm/core-common";
|
|
29
|
+
|
|
30
|
+
if (env.DEV) {
|
|
31
|
+
// development-only logic
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`Version: ${env.VER}`);
|
|
35
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Error Utilities
|
|
2
|
+
|
|
3
|
+
Imported as the `err` namespace. Utilities for handling unknown error values.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { err } from "@simplysm/core-common";
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## message
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
function message(err: unknown): string;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Extracts a message string from an unknown error value (typically from a `catch` block). Returns `err.message` for `Error` instances, otherwise returns `String(err)`.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Usage Examples
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { err } from "@simplysm/core-common";
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
throw new Error("something failed");
|
|
26
|
+
} catch (e) {
|
|
27
|
+
const msg = err.message(e); // "something failed"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
throw "raw string error";
|
|
32
|
+
} catch (e) {
|
|
33
|
+
const msg = err.message(e); // "raw string error"
|
|
34
|
+
}
|
|
35
|
+
```
|
package/docs/errors.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Errors
|
|
2
|
+
|
|
3
|
+
Custom error class hierarchy with tree-structured cause chaining.
|
|
4
|
+
|
|
5
|
+
## SdError
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
class SdError extends Error {
|
|
9
|
+
cause?: Error;
|
|
10
|
+
|
|
11
|
+
constructor(cause: Error, ...messages: string[]);
|
|
12
|
+
constructor(...messages: string[]);
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Base error class supporting tree-structured cause chaining. Messages are joined in reverse order with ` => ` separator. The cause stack trace is appended to the current stack.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ArgumentError
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
class ArgumentError extends SdError {
|
|
24
|
+
constructor(argObj: Record<string, unknown>);
|
|
25
|
+
constructor(message: string, argObj: Record<string, unknown>);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Error for invalid arguments. Includes the argument object formatted as YAML in the message for easy debugging.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## NotImplementedError
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
class NotImplementedError extends SdError {
|
|
37
|
+
constructor(message?: string);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Error for features not yet implemented. Used for abstract method stubs and planned branches.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## TimeoutError
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
class TimeoutError extends SdError {
|
|
49
|
+
constructor(count?: number, message?: string);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Error thrown when waiting time is exceeded. Automatically thrown by `wait.until()` when the maximum number of attempts is reached.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Usage Examples
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { SdError, ArgumentError, NotImplementedError, TimeoutError } from "@simplysm/core-common";
|
|
61
|
+
|
|
62
|
+
// Wrap a cause error
|
|
63
|
+
try {
|
|
64
|
+
await fetch(url);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
throw new SdError(err, "API call failed", "User load failed");
|
|
67
|
+
// message: "User load failed => API call failed => original error message"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ArgumentError with YAML-formatted details
|
|
71
|
+
throw new ArgumentError("Invalid user", { userId: 123, name: null });
|
|
72
|
+
// message: "Invalid user\n\nuserId: 123\nname: null"
|
|
73
|
+
|
|
74
|
+
// NotImplementedError
|
|
75
|
+
throw new NotImplementedError("PDF export");
|
|
76
|
+
|
|
77
|
+
// TimeoutError
|
|
78
|
+
throw new TimeoutError(50, "Waiting for API response");
|
|
79
|
+
```
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Extensions
|
|
2
|
+
|
|
3
|
+
Prototype extensions for `Array`, `Map`, and `Set`. These are activated as side effects when importing from `@simplysm/core-common`.
|
|
4
|
+
|
|
5
|
+
## Array Extensions (ReadonlyArray)
|
|
6
|
+
|
|
7
|
+
### Query
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
interface ReadonlyArray<T> {
|
|
11
|
+
single(predicate?: (item: T, index: number) => boolean): T | undefined;
|
|
12
|
+
first(predicate?: (item: T, index: number) => boolean): T | undefined;
|
|
13
|
+
last(predicate?: (item: T, index: number) => boolean): T | undefined;
|
|
14
|
+
filterExists(): NonNullable<T>[];
|
|
15
|
+
ofType<K extends PrimitiveTypeStr>(type: K): Extract<T, PrimitiveTypeMap[K]>[];
|
|
16
|
+
ofType<N extends T>(type: Type<N>): N[];
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `single` - Returns the only matching element; throws `ArgumentError` if more than one match.
|
|
21
|
+
- `first` / `last` - Returns the first/last matching element, or `undefined`.
|
|
22
|
+
- `filterExists` - Removes `null` and `undefined` values.
|
|
23
|
+
- `ofType` - Filters elements by primitive type string or constructor type.
|
|
24
|
+
|
|
25
|
+
### Async Operations
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
interface ReadonlyArray<T> {
|
|
29
|
+
filterAsync(predicate: (item: T, index: number) => Promise<boolean>): Promise<T[]>;
|
|
30
|
+
mapAsync<R>(selector: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
31
|
+
mapManyAsync<R>(selector: (item: T, index: number) => Promise<R[]>): Promise<R[]>;
|
|
32
|
+
parallelAsync<R>(fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `filterAsync` / `mapAsync` / `mapManyAsync` - Sequential async operations (one at a time).
|
|
37
|
+
- `parallelAsync` - Parallel async via `Promise.all`.
|
|
38
|
+
|
|
39
|
+
### Transformation
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
interface ReadonlyArray<T> {
|
|
43
|
+
mapMany<R>(selector?: (item: T, index: number) => R[]): R[];
|
|
44
|
+
groupBy<K>(keySelector: (item: T, index: number) => K): { key: K; values: T[] }[];
|
|
45
|
+
groupBy<K, V>(keySelector: ..., valueSelector: ...): { key: K; values: V[] }[];
|
|
46
|
+
toMap<K>(keySelector: ...): Map<K, T>;
|
|
47
|
+
toMap<K, V>(keySelector: ..., valueSelector: ...): Map<K, V>;
|
|
48
|
+
toMapAsync<K>(keySelector: ...): Promise<Map<K, T>>;
|
|
49
|
+
toArrayMap<K>(keySelector: ...): Map<K, T[]>;
|
|
50
|
+
toSetMap<K>(keySelector: ...): Map<K, Set<T>>;
|
|
51
|
+
toMapValues<K, V>(keySelector: ..., valueSelector: ...): Map<K, V>;
|
|
52
|
+
toObject(keySelector: ...): Record<string, T>;
|
|
53
|
+
toObject<V>(keySelector: ..., valueSelector: ...): Record<string, V>;
|
|
54
|
+
toTree<K extends keyof T, P extends keyof T>(keyProp: K, parentKey: P): TreeArray<T>[];
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `groupBy` - Groups by key. O(n) for primitive keys, O(n^2) for object keys.
|
|
59
|
+
- `toMap` - Converts to `Map` (throws on duplicate keys).
|
|
60
|
+
- `toArrayMap` / `toSetMap` - Converts to `Map<K, T[]>` or `Map<K, Set<T>>`.
|
|
61
|
+
- `toTree` - Converts flat array to tree structure using key/parent-key properties.
|
|
62
|
+
|
|
63
|
+
### Ordering and Deduplication
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
interface ReadonlyArray<T> {
|
|
67
|
+
distinct(options?: boolean | { matchAddress?: boolean; keyFn?: (item: T) => string | number }): T[];
|
|
68
|
+
orderBy(selector?: (item: T) => ComparableType): T[];
|
|
69
|
+
orderByDesc(selector?: (item: T) => ComparableType): T[];
|
|
70
|
+
shuffle(): T[];
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
These return new arrays without modifying the original.
|
|
75
|
+
|
|
76
|
+
### Aggregation
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface ReadonlyArray<T> {
|
|
80
|
+
sum(selector?: (item: T, index: number) => number): number;
|
|
81
|
+
min(selector?: ...): number | string | undefined;
|
|
82
|
+
max(selector?: ...): number | string | undefined;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Diff and Merge
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
interface ReadonlyArray<T> {
|
|
90
|
+
diffs<P>(target: P[], options?: { keys?: string[]; excludes?: string[] }): ArrayDiffsResult<T, P>[];
|
|
91
|
+
oneWayDiffs<K extends keyof T>(orgItems: T[] | Map<T[K], T>, keyPropNameOrGetValFn: K | ((item: T) => ...), options?: ...): ArrayOneWayDiffResult<T>[];
|
|
92
|
+
merge<P>(target: P[], options?: { keys?: string[]; excludes?: string[] }): (T | P | (T & P))[];
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `diffs` - Computes INSERT/DELETE/UPDATE differences between two arrays.
|
|
97
|
+
- `oneWayDiffs` - One-way diff returning `create` / `update` / `same` results.
|
|
98
|
+
- `merge` - Deep merges matching elements from target into source.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Array Extensions (Mutable)
|
|
103
|
+
|
|
104
|
+
These methods modify the original array in-place and return `this` for chaining.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
interface Array<T> {
|
|
108
|
+
distinctThis(options?: ...): T[];
|
|
109
|
+
orderByThis(selector?: ...): T[];
|
|
110
|
+
orderByDescThis(selector?: ...): T[];
|
|
111
|
+
insert(index: number, ...items: T[]): this;
|
|
112
|
+
remove(itemOrSelector: T | ((item: T, index: number) => boolean)): this;
|
|
113
|
+
toggle(item: T): this;
|
|
114
|
+
clear(): this;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Map Extensions
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
interface Map<K, V> {
|
|
124
|
+
getOrCreate(key: K, newValue: V): V;
|
|
125
|
+
getOrCreate(key: K, newValueFn: () => V): V;
|
|
126
|
+
update(key: K, updateFn: (v: V | undefined) => V): void;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- `getOrCreate` - Returns existing value, or creates and stores a new one. Accepts a direct value or a factory function.
|
|
131
|
+
- `update` - Updates value using a function that receives the current value (or `undefined` if key is missing).
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Set Extensions
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
interface Set<T> {
|
|
139
|
+
adds(...values: T[]): this;
|
|
140
|
+
toggle(value: T, addOrDel?: "add" | "del"): this;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- `adds` - Adds multiple values at once.
|
|
145
|
+
- `toggle` - Toggles value presence. Optional `addOrDel` parameter forces add or delete.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Exported Types
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
type ArrayDiffsResult<T, P> =
|
|
153
|
+
| { source: undefined; target: P } // INSERT
|
|
154
|
+
| { source: T; target: undefined } // DELETE
|
|
155
|
+
| { source: T; target: P }; // UPDATE
|
|
156
|
+
|
|
157
|
+
type ArrayOneWayDiffResult<T> =
|
|
158
|
+
| { type: "create"; item: T; orgItem: undefined }
|
|
159
|
+
| { type: "update"; item: T; orgItem: T }
|
|
160
|
+
| { type: "same"; item: T; orgItem: T };
|
|
161
|
+
|
|
162
|
+
type TreeArray<T> = T & { children: TreeArray<T>[] };
|
|
163
|
+
type ComparableType = string | number | boolean | DateTime | DateOnly | Time | undefined;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Usage Examples
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import "@simplysm/core-common";
|
|
172
|
+
|
|
173
|
+
// Array extensions
|
|
174
|
+
const items = [3, 1, 4, 1, 5, 9];
|
|
175
|
+
items.distinct(); // [3, 1, 4, 5, 9]
|
|
176
|
+
items.orderBy(); // [1, 1, 3, 4, 5, 9]
|
|
177
|
+
items.sum(); // 23
|
|
178
|
+
items.first((x) => x > 3); // 4
|
|
179
|
+
|
|
180
|
+
const users = [
|
|
181
|
+
{ id: 1, dept: "A", name: "Alice" },
|
|
182
|
+
{ id: 2, dept: "B", name: "Bob" },
|
|
183
|
+
{ id: 3, dept: "A", name: "Charlie" },
|
|
184
|
+
];
|
|
185
|
+
users.groupBy((u) => u.dept);
|
|
186
|
+
// [{ key: "A", values: [Alice, Charlie] }, { key: "B", values: [Bob] }]
|
|
187
|
+
|
|
188
|
+
users.toMap((u) => u.id);
|
|
189
|
+
// Map { 1 => Alice, 2 => Bob, 3 => Charlie }
|
|
190
|
+
|
|
191
|
+
// Map extensions
|
|
192
|
+
const map = new Map<string, number>();
|
|
193
|
+
map.getOrCreate("counter", 0); // 0
|
|
194
|
+
map.update("counter", (v) => (v ?? 0) + 1); // counter = 1
|
|
195
|
+
|
|
196
|
+
// Set extensions
|
|
197
|
+
const set = new Set([1, 2, 3]);
|
|
198
|
+
set.toggle(2); // removes 2 -> {1, 3}
|
|
199
|
+
set.toggle(4); // adds 4 -> {1, 3, 4}
|
|
200
|
+
set.adds(5, 6); // {1, 3, 4, 5, 6}
|
|
201
|
+
```
|
package/docs/features.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Features
|
|
2
|
+
|
|
3
|
+
Async control primitives: type-safe event emitter, debounce queue, and serial queue.
|
|
4
|
+
|
|
5
|
+
## EventEmitter
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
class EventEmitter<TEvents extends { [K in keyof TEvents]: unknown } = Record<string, unknown>> {
|
|
9
|
+
on<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void;
|
|
10
|
+
off<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void;
|
|
11
|
+
emit<K extends keyof TEvents & string>(type: K, ...args: TEvents[K] extends void ? [] : [data: TEvents[K]]): void;
|
|
12
|
+
listenerCount<K extends keyof TEvents & string>(type: K): number;
|
|
13
|
+
dispose(): void;
|
|
14
|
+
[Symbol.dispose](): void;
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A type-safe event emitter that works in both browsers and Node.js. Internally implemented using `EventTarget`. Duplicate registration of the same listener to the same event is ignored.
|
|
19
|
+
|
|
20
|
+
Use `void` as the event data type for events that carry no data -- `emit("done")` is called without arguments.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## DebounceQueue
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
class DebounceQueue extends EventEmitter<{ error: SdError }> {
|
|
28
|
+
constructor(delay?: number);
|
|
29
|
+
run(fn: () => void | Promise<void>): void;
|
|
30
|
+
dispose(): void;
|
|
31
|
+
[Symbol.dispose](): void;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
When called multiple times within a short time, only the last request is executed. Previous requests are replaced. If `delay` is omitted, executes on the next event loop tick.
|
|
36
|
+
|
|
37
|
+
Requests added during execution are processed immediately after the current execution completes (no debounce delay for mid-execution arrivals). Errors are emitted as `"error"` events; if no listener is registered, they are logged.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## SerialQueue
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
class SerialQueue extends EventEmitter<{ error: SdError }> {
|
|
45
|
+
constructor(gap?: number);
|
|
46
|
+
run(fn: () => void | Promise<void>): void;
|
|
47
|
+
dispose(): void;
|
|
48
|
+
[Symbol.dispose](): void;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Functions added to the queue are executed sequentially -- the next task starts only after the previous one completes. Subsequent tasks continue even if an error occurs. The optional `gap` parameter adds a delay between each task. Errors are emitted as `"error"` events.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Usage Examples
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { EventEmitter, DebounceQueue, SerialQueue } from "@simplysm/core-common";
|
|
60
|
+
|
|
61
|
+
// EventEmitter
|
|
62
|
+
interface MyEvents {
|
|
63
|
+
data: string;
|
|
64
|
+
error: Error;
|
|
65
|
+
done: void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class MyEmitter extends EventEmitter<MyEvents> {}
|
|
69
|
+
|
|
70
|
+
const emitter = new MyEmitter();
|
|
71
|
+
emitter.on("data", (data) => { /* data: string */ });
|
|
72
|
+
emitter.emit("data", "hello");
|
|
73
|
+
emitter.emit("done"); // void type, no arguments
|
|
74
|
+
|
|
75
|
+
// DebounceQueue
|
|
76
|
+
using queue = new DebounceQueue(300); // 300ms delay
|
|
77
|
+
queue.on("error", (err) => { /* handle error */ });
|
|
78
|
+
queue.run(() => { /* ignored */ });
|
|
79
|
+
queue.run(() => { /* ignored */ });
|
|
80
|
+
queue.run(() => { /* executed after 300ms */ });
|
|
81
|
+
|
|
82
|
+
// SerialQueue
|
|
83
|
+
using serial = new SerialQueue();
|
|
84
|
+
serial.on("error", (err) => { /* handle error */ });
|
|
85
|
+
serial.run(async () => { await fetch("/api/1"); });
|
|
86
|
+
serial.run(async () => { await fetch("/api/2"); }); // runs after 1 completes
|
|
87
|
+
serial.run(async () => { await fetch("/api/3"); }); // runs after 2 completes
|
|
88
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# JSON Utilities
|
|
2
|
+
|
|
3
|
+
Imported as the `json` namespace. JSON serialization/deserialization with full support for custom types.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { json } from "@simplysm/core-common";
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## stringify
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
function stringify(obj: unknown, options?: {
|
|
13
|
+
space?: string | number;
|
|
14
|
+
replacer?: (key: string | undefined, value: unknown) => unknown;
|
|
15
|
+
redactBytes?: boolean;
|
|
16
|
+
}): string;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Serializes an object to JSON string with custom type support. Special types are encoded as `{ __type__: "TypeName", data: ... }`.
|
|
20
|
+
|
|
21
|
+
**Supported types:** `Date`, `DateTime`, `DateOnly`, `Time`, `Uuid`, `Set`, `Map`, `Error`, `Uint8Array`.
|
|
22
|
+
|
|
23
|
+
- `redactBytes` replaces `Uint8Array` contents with `"__hidden__"` (for logging).
|
|
24
|
+
- Objects with `toJSON()` are honored (except built-in special types).
|
|
25
|
+
- Throws `TypeError` on circular references.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## parse
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
function parse<T = unknown>(json: string): T;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Deserializes a JSON string, restoring all custom types from `__type__` markers. All JSON `null` values are converted to `undefined` (simplysm framework convention).
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Usage Examples
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { json, DateTime, Uuid } from "@simplysm/core-common";
|
|
43
|
+
|
|
44
|
+
const data = {
|
|
45
|
+
id: Uuid.generate(),
|
|
46
|
+
createdAt: new DateTime(2025, 1, 15),
|
|
47
|
+
tags: new Set(["a", "b"]),
|
|
48
|
+
meta: new Map([["key", "value"]]),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const str = json.stringify(data, { space: 2 });
|
|
52
|
+
const restored = json.parse(str);
|
|
53
|
+
// restored.id is Uuid, restored.createdAt is DateTime, etc.
|
|
54
|
+
|
|
55
|
+
// Redact binary data for logging
|
|
56
|
+
json.stringify({ file: new Uint8Array([1, 2, 3]) }, { redactBytes: true });
|
|
57
|
+
```
|