@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 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
+ ```
@@ -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
+ ```