ts-data-forge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +534 -0
  3. package/package.json +101 -0
  4. package/src/array/array-utils-creation.test.mts +443 -0
  5. package/src/array/array-utils-modification.test.mts +197 -0
  6. package/src/array/array-utils-overload-type-error.test.mts +149 -0
  7. package/src/array/array-utils-reducing-value.test.mts +425 -0
  8. package/src/array/array-utils-search.test.mts +169 -0
  9. package/src/array/array-utils-set-op.test.mts +335 -0
  10. package/src/array/array-utils-slice-clamped.test.mts +113 -0
  11. package/src/array/array-utils-slicing.test.mts +316 -0
  12. package/src/array/array-utils-transformation.test.mts +790 -0
  13. package/src/array/array-utils-validation.test.mts +492 -0
  14. package/src/array/array-utils.mts +4000 -0
  15. package/src/array/array.test.mts +146 -0
  16. package/src/array/index.mts +2 -0
  17. package/src/array/tuple-utils.mts +519 -0
  18. package/src/array/tuple-utils.test.mts +518 -0
  19. package/src/collections/imap-mapped.mts +801 -0
  20. package/src/collections/imap-mapped.test.mts +860 -0
  21. package/src/collections/imap.mts +651 -0
  22. package/src/collections/imap.test.mts +932 -0
  23. package/src/collections/index.mts +6 -0
  24. package/src/collections/iset-mapped.mts +889 -0
  25. package/src/collections/iset-mapped.test.mts +1187 -0
  26. package/src/collections/iset.mts +682 -0
  27. package/src/collections/iset.test.mts +1084 -0
  28. package/src/collections/queue.mts +390 -0
  29. package/src/collections/queue.test.mts +282 -0
  30. package/src/collections/stack.mts +423 -0
  31. package/src/collections/stack.test.mts +225 -0
  32. package/src/expect-type.mts +206 -0
  33. package/src/functional/index.mts +4 -0
  34. package/src/functional/match.mts +300 -0
  35. package/src/functional/match.test.mts +177 -0
  36. package/src/functional/optional.mts +733 -0
  37. package/src/functional/optional.test.mts +619 -0
  38. package/src/functional/pipe.mts +212 -0
  39. package/src/functional/pipe.test.mts +85 -0
  40. package/src/functional/result.mts +1134 -0
  41. package/src/functional/result.test.mts +777 -0
  42. package/src/globals.d.mts +38 -0
  43. package/src/guard/has-key.mts +119 -0
  44. package/src/guard/has-key.test.mts +219 -0
  45. package/src/guard/index.mts +7 -0
  46. package/src/guard/is-non-empty-string.mts +108 -0
  47. package/src/guard/is-non-empty-string.test.mts +91 -0
  48. package/src/guard/is-non-null-object.mts +106 -0
  49. package/src/guard/is-non-null-object.test.mts +90 -0
  50. package/src/guard/is-primitive.mts +165 -0
  51. package/src/guard/is-primitive.test.mts +102 -0
  52. package/src/guard/is-record.mts +153 -0
  53. package/src/guard/is-record.test.mts +112 -0
  54. package/src/guard/is-type.mts +450 -0
  55. package/src/guard/is-type.test.mts +496 -0
  56. package/src/guard/key-is-in.mts +163 -0
  57. package/src/guard/key-is-in.test.mts +19 -0
  58. package/src/index.mts +10 -0
  59. package/src/iterator/index.mts +1 -0
  60. package/src/iterator/range.mts +120 -0
  61. package/src/iterator/range.test.mts +33 -0
  62. package/src/json/index.mts +1 -0
  63. package/src/json/json.mts +711 -0
  64. package/src/json/json.test.mts +628 -0
  65. package/src/number/branded-types/finite-number.mts +354 -0
  66. package/src/number/branded-types/finite-number.test.mts +135 -0
  67. package/src/number/branded-types/index.mts +26 -0
  68. package/src/number/branded-types/int.mts +278 -0
  69. package/src/number/branded-types/int.test.mts +140 -0
  70. package/src/number/branded-types/int16.mts +192 -0
  71. package/src/number/branded-types/int16.test.mts +170 -0
  72. package/src/number/branded-types/int32.mts +193 -0
  73. package/src/number/branded-types/int32.test.mts +170 -0
  74. package/src/number/branded-types/non-negative-finite-number.mts +223 -0
  75. package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
  76. package/src/number/branded-types/non-negative-int16.mts +187 -0
  77. package/src/number/branded-types/non-negative-int16.test.mts +201 -0
  78. package/src/number/branded-types/non-negative-int32.mts +187 -0
  79. package/src/number/branded-types/non-negative-int32.test.mts +204 -0
  80. package/src/number/branded-types/non-zero-finite-number.mts +229 -0
  81. package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
  82. package/src/number/branded-types/non-zero-int.mts +167 -0
  83. package/src/number/branded-types/non-zero-int.test.mts +177 -0
  84. package/src/number/branded-types/non-zero-int16.mts +196 -0
  85. package/src/number/branded-types/non-zero-int16.test.mts +195 -0
  86. package/src/number/branded-types/non-zero-int32.mts +196 -0
  87. package/src/number/branded-types/non-zero-int32.test.mts +197 -0
  88. package/src/number/branded-types/non-zero-safe-int.mts +196 -0
  89. package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
  90. package/src/number/branded-types/non-zero-uint16.mts +189 -0
  91. package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
  92. package/src/number/branded-types/non-zero-uint32.mts +189 -0
  93. package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
  94. package/src/number/branded-types/positive-finite-number.mts +241 -0
  95. package/src/number/branded-types/positive-finite-number.test.mts +204 -0
  96. package/src/number/branded-types/positive-int.mts +304 -0
  97. package/src/number/branded-types/positive-int.test.mts +176 -0
  98. package/src/number/branded-types/positive-int16.mts +188 -0
  99. package/src/number/branded-types/positive-int16.test.mts +197 -0
  100. package/src/number/branded-types/positive-int32.mts +188 -0
  101. package/src/number/branded-types/positive-int32.test.mts +197 -0
  102. package/src/number/branded-types/positive-safe-int.mts +187 -0
  103. package/src/number/branded-types/positive-safe-int.test.mts +210 -0
  104. package/src/number/branded-types/positive-uint16.mts +188 -0
  105. package/src/number/branded-types/positive-uint16.test.mts +203 -0
  106. package/src/number/branded-types/positive-uint32.mts +188 -0
  107. package/src/number/branded-types/positive-uint32.test.mts +203 -0
  108. package/src/number/branded-types/safe-int.mts +291 -0
  109. package/src/number/branded-types/safe-int.test.mts +170 -0
  110. package/src/number/branded-types/safe-uint.mts +187 -0
  111. package/src/number/branded-types/safe-uint.test.mts +176 -0
  112. package/src/number/branded-types/uint.mts +179 -0
  113. package/src/number/branded-types/uint.test.mts +158 -0
  114. package/src/number/branded-types/uint16.mts +186 -0
  115. package/src/number/branded-types/uint16.test.mts +170 -0
  116. package/src/number/branded-types/uint32.mts +218 -0
  117. package/src/number/branded-types/uint32.test.mts +170 -0
  118. package/src/number/enum/index.mts +2 -0
  119. package/src/number/enum/int8.mts +344 -0
  120. package/src/number/enum/int8.test.mts +180 -0
  121. package/src/number/enum/uint8.mts +293 -0
  122. package/src/number/enum/uint8.test.mts +164 -0
  123. package/src/number/index.mts +4 -0
  124. package/src/number/num.mts +604 -0
  125. package/src/number/num.test.mts +242 -0
  126. package/src/number/refined-number-utils.mts +566 -0
  127. package/src/object/index.mts +1 -0
  128. package/src/object/object.mts +447 -0
  129. package/src/object/object.test.mts +124 -0
  130. package/src/others/cast-mutable.mts +113 -0
  131. package/src/others/cast-readonly.mts +192 -0
  132. package/src/others/cast-readonly.test.mts +89 -0
  133. package/src/others/if-then.mts +98 -0
  134. package/src/others/if-then.test.mts +75 -0
  135. package/src/others/index.mts +7 -0
  136. package/src/others/map-nullable.mts +172 -0
  137. package/src/others/map-nullable.test.mts +297 -0
  138. package/src/others/memoize-function.mts +196 -0
  139. package/src/others/memoize-function.test.mts +168 -0
  140. package/src/others/tuple.mts +160 -0
  141. package/src/others/tuple.test.mts +11 -0
  142. package/src/others/unknown-to-string.mts +215 -0
  143. package/src/others/unknown-to-string.test.mts +114 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Creates a readonly tuple from the given arguments with precise literal type inference.
3
+ *
4
+ * This utility function provides a concise way to create tuples while preserving
5
+ * exact literal types. Without this function, TypeScript would infer arrays with
6
+ * widened types instead of tuples with literal types.
7
+ *
8
+ * **Key benefits:**
9
+ * - Preserves literal types (e.g., `1` instead of `number`)
10
+ * - Creates readonly tuples for immutability
11
+ * - Provides better type inference than array literals
12
+ * - Zero runtime overhead - just returns the arguments
13
+ *
14
+ * @template T - A tuple type with literal types inferred from the arguments
15
+ * @param args - The elements to include in the tuple (variadic)
16
+ * @returns A readonly tuple containing the provided arguments with preserved literal types
17
+ *
18
+ * @example Basic tuple creation with literal types
19
+ * ```typescript
20
+ * // Without tp: types are widened
21
+ * const arr = [1, 'hello', true]; // (string | number | boolean)[]
22
+ *
23
+ * // With tp: exact literal types preserved
24
+ * const tuple = tp(1, 'hello', true); // readonly [1, 'hello', true]
25
+ * const coords = tp(10, 20); // readonly [10, 20]
26
+ * const single = tp('only'); // readonly ['only']
27
+ * const empty = tp(); // readonly []
28
+ *
29
+ * // TypeScript knows exact values at compile time
30
+ * type First = typeof tuple[0]; // 1 (literal type)
31
+ * type Second = typeof tuple[1]; // 'hello' (literal type)
32
+ * ```
33
+ *
34
+ * @example Creating type-safe coordinate systems
35
+ * ```typescript
36
+ * // 2D coordinates
37
+ * const point2D = tp(10, 20);
38
+ * const [x, y] = point2D; // x: 10, y: 20
39
+ *
40
+ * // 3D coordinates
41
+ * const point3D = tp(10, 20, 30);
42
+ * const [x3, y3, z3] = point3D; // Exact types preserved
43
+ *
44
+ * // Named coordinate system
45
+ * const namedPoint = tp('x', 100, 'y', 200);
46
+ * // Type: readonly ['x', 100, 'y', 200]
47
+ * ```
48
+ *
49
+ * @example Building type-safe data structures
50
+ * ```typescript
51
+ * // Creating a type-safe map structure
52
+ * const colorMap = [
53
+ * tp('red', '#FF0000'),
54
+ * tp('green', '#00FF00'),
55
+ * tp('blue', '#0000FF')
56
+ * ] as const;
57
+ * // Type: readonly [readonly ['red', '#FF0000'], ...]
58
+ *
59
+ * // Type-safe event system
60
+ * type EventTuple = readonly ['click', MouseEvent] | readonly ['change', Event];
61
+ * const event = tp('click', new MouseEvent('click')) as EventTuple;
62
+ * ```
63
+ *
64
+ * @example Function argument patterns
65
+ * ```typescript
66
+ * // Functions expecting exact tuple types
67
+ * function processCoordinate(coord: readonly [number, number]): void {
68
+ * const [x, y] = coord;
69
+ * console.log(`Processing point at (${x}, ${y})`);
70
+ * }
71
+ *
72
+ * processCoordinate(tp(10, 20)); // ✅ Type-safe
73
+ * processCoordinate([10, 20]); // ❌ Error: number[] not assignable
74
+ *
75
+ * // Pattern matching with tuples
76
+ * function handleMessage(msg: readonly ['error', string] | readonly ['success', any]) {
77
+ * if (msg[0] === 'error') {
78
+ * console.error(msg[1]); // TypeScript knows msg[1] is string
79
+ * } else {
80
+ * console.log('Success:', msg[1]);
81
+ * }
82
+ * }
83
+ *
84
+ * handleMessage(tp('error', 'Failed to load'));
85
+ * handleMessage(tp('success', { id: 123 }));
86
+ * ```
87
+ *
88
+ * @example Advanced type inference
89
+ * ```typescript
90
+ * // Const assertions comparison
91
+ * const tuple1 = [1, 2, 3]; // number[]
92
+ * const tuple2 = [1, 2, 3] as const; // readonly [1, 2, 3]
93
+ * const tuple3 = tp(1, 2, 3); // readonly [1, 2, 3]
94
+ *
95
+ * // Building complex types
96
+ * const config = tp(
97
+ * tp('host', 'localhost'),
98
+ * tp('port', 3000),
99
+ * tp('secure', true)
100
+ * );
101
+ * // Type: readonly [
102
+ * // readonly ['host', 'localhost'],
103
+ * // readonly ['port', 3000],
104
+ * // readonly ['secure', true]
105
+ * // ]
106
+ *
107
+ * // Type-safe destructuring
108
+ * const [[, host], [, port], [, secure]] = config;
109
+ * // host: 'localhost', port: 3000, secure: true
110
+ * ```
111
+ *
112
+ * @example Integration with other utilities
113
+ * ```typescript
114
+ * import { pipe } from '../functional/pipe';
115
+ * import { Result } from '../functional/result';
116
+ *
117
+ * // Type-safe error handling
118
+ * function divide(a: number, b: number): Result<number, string> {
119
+ * if (b === 0) return Result.err('Division by zero');
120
+ * return Result.ok(a / b);
121
+ * }
122
+ *
123
+ * const calculation = tp(10, 2);
124
+ * const result = divide(...calculation); // Spread tuple as arguments
125
+ *
126
+ * // Building pipelines with tuples
127
+ * const pipeline = pipe(tp(5, 10))
128
+ * .map(([a, b]) => tp(a + b, a * b))
129
+ * .map(([sum, product]) => tp('sum', sum, 'product', product))
130
+ * .value;
131
+ * // Type: readonly ['sum', 15, 'product', 50]
132
+ * ```
133
+ *
134
+ * @example Common patterns and use cases
135
+ * ```typescript
136
+ * // React-style state tuples
137
+ * const useState = <T>(initial: T) => tp(initial, (value: T) => void 0);
138
+ * const [count, setCount] = useState(0);
139
+ *
140
+ * // Redux-style actions
141
+ * const incrementAction = tp('INCREMENT', { amount: 1 });
142
+ * const decrementAction = tp('DECREMENT', { amount: 1 });
143
+ *
144
+ * // Database query results
145
+ * const queryResult = tp(
146
+ * true, // success
147
+ * [{ id: 1, name: 'John' }], // data
148
+ * null // error
149
+ * );
150
+ *
151
+ * // Configuration flags
152
+ * const features = tp('darkMode', 'analytics', 'notifications');
153
+ * const enabledFeatures = features.filter(f => isEnabled(f));
154
+ * ```
155
+ *
156
+ * @see https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types
157
+ */
158
+ export const tp = <const T extends readonly unknown[]>(
159
+ ...args: T
160
+ ): Readonly<T> => args;
@@ -0,0 +1,11 @@
1
+ import { expectType } from '../expect-type.mjs';
2
+ import { tp } from './tuple.mjs';
3
+
4
+ describe('tp', () => {
5
+ test('test type', () => {
6
+ const tuple = tp(1, 2, 3);
7
+ expect(tuple).toStrictEqual([1, 2, 3]);
8
+
9
+ expectType<typeof tuple, readonly [1, 2, 3]>('=');
10
+ });
11
+ });
@@ -0,0 +1,215 @@
1
+ import { Result } from '../functional/index.mjs';
2
+ import { isNonNullish } from '../guard/index.mjs';
3
+
4
+ /**
5
+ * Converts an unknown value to its string representation in a type-safe manner.
6
+ *
7
+ * This function handles all JavaScript types and provides consistent string conversion
8
+ * with proper error handling for edge cases like circular references. Unlike naive
9
+ * toString() calls, this function never throws and handles all value types gracefully.
10
+ *
11
+ * **Type conversion rules:**
12
+ * - Strings: returned as-is
13
+ * - Numbers, booleans, bigints: converted via toString()
14
+ * - Symbols: converted to their description string
15
+ * - Functions: converted to their string representation
16
+ * - null: returns "null" (not "null" from JSON)
17
+ * - undefined: returns "undefined"
18
+ * - Objects: JSON stringified (with optional pretty printing)
19
+ *
20
+ * @param value - The unknown value to convert to string
21
+ * @param options - Optional configuration for the conversion
22
+ * @param options.prettyPrintObject - If true, objects are formatted with 2-space indentation
23
+ * @returns A Result containing either the string representation or an Error for failures
24
+ *
25
+ * @example Basic type conversions
26
+ * ```typescript
27
+ * // Primitive types
28
+ * unknownToString('hello'); // Ok('hello')
29
+ * unknownToString(123); // Ok('123')
30
+ * unknownToString(true); // Ok('true')
31
+ * unknownToString(null); // Ok('null')
32
+ * unknownToString(undefined); // Ok('undefined')
33
+ * unknownToString(Symbol('test')); // Ok('Symbol(test)')
34
+ * unknownToString(123n); // Ok('123')
35
+ *
36
+ * // Function conversion
37
+ * const fn = () => 'test';
38
+ * unknownToString(fn); // Ok('() => 'test'')
39
+ * ```
40
+ *
41
+ * @example Object stringification
42
+ * ```typescript
43
+ * // Simple object
44
+ * const obj = { a: 1, b: 'hello', c: [1, 2, 3] };
45
+ * const result = unknownToString(obj);
46
+ * if (Result.isOk(result)) {
47
+ * console.log(result.value); // '{"a":1,"b":"hello","c":[1,2,3]}'
48
+ * }
49
+ *
50
+ * // Pretty printing
51
+ * const prettyResult = unknownToString(obj, { prettyPrintObject: true });
52
+ * if (Result.isOk(prettyResult)) {
53
+ * console.log(prettyResult.value);
54
+ * // {
55
+ * // "a": 1,
56
+ * // "b": "hello",
57
+ * // "c": [
58
+ * // 1,
59
+ * // 2,
60
+ * // 3
61
+ * // ]
62
+ * // }
63
+ * }
64
+ * ```
65
+ *
66
+ * @example Error handling for circular references
67
+ * ```typescript
68
+ * // Circular reference
69
+ * const circular: any = { name: 'parent' };
70
+ * circular.self = circular;
71
+ *
72
+ * const result = unknownToString(circular);
73
+ * if (Result.isErr(result)) {
74
+ * console.log(result.value.message);
75
+ * // "Converting circular structure to JSON"
76
+ * }
77
+ *
78
+ * // Handle with custom serialization
79
+ * const safeStringify = (value: unknown): string => {
80
+ * const result = unknownToString(value);
81
+ * return Result.isOk(result)
82
+ * ? result.value
83
+ * : `[Error: ${result.value.message}]`;
84
+ * };
85
+ * ```
86
+ *
87
+ * @example Logging and debugging utilities
88
+ * ```typescript
89
+ * // Type-safe logger
90
+ * class Logger {
91
+ * log(message: string, data?: unknown): void {
92
+ * const timestamp = new Date().toISOString();
93
+ * const dataStr = data !== undefined
94
+ * ? unknownToString(data, { prettyPrintObject: true })
95
+ * : Result.ok('');
96
+ *
97
+ * if (Result.isOk(dataStr)) {
98
+ * console.log(`[${timestamp}] ${message}`, dataStr.value);
99
+ * } else {
100
+ * console.log(`[${timestamp}] ${message} [Unstringifiable data]`);
101
+ * }
102
+ * }
103
+ * }
104
+ *
105
+ * const logger = new Logger();
106
+ * logger.log('User data:', { id: 123, name: 'John' });
107
+ * ```
108
+ *
109
+ * @example API response formatting
110
+ * ```typescript
111
+ * // Safe error response formatting
112
+ * function formatErrorResponse(error: unknown): string {
113
+ * const result = unknownToString(error, { prettyPrintObject: true });
114
+ *
115
+ * if (Result.isOk(result)) {
116
+ * return JSON.stringify({
117
+ * success: false,
118
+ * error: result.value
119
+ * });
120
+ * }
121
+ *
122
+ * // Fallback for unstringifiable errors
123
+ * return JSON.stringify({
124
+ * success: false,
125
+ * error: 'An unknown error occurred'
126
+ * });
127
+ * }
128
+ *
129
+ * try {
130
+ * // some operation
131
+ * } catch (error) {
132
+ * const response = formatErrorResponse(error);
133
+ * res.status(500).send(response);
134
+ * }
135
+ * ```
136
+ *
137
+ * @example Working with special objects
138
+ * ```typescript
139
+ * // Date objects
140
+ * unknownToString(new Date('2023-01-01'));
141
+ * // Ok('"2023-01-01T00:00:00.000Z"') - JSON stringified
142
+ *
143
+ * // Regular expressions
144
+ * unknownToString(/test/gi);
145
+ * // Ok('{}') - RegExp has no enumerable properties
146
+ *
147
+ * // Arrays
148
+ * unknownToString([1, 'two', { three: 3 }]);
149
+ * // Ok('[1,"two",{"three":3}]')
150
+ *
151
+ * // Map and Set (converted to empty objects by JSON.stringify)
152
+ * unknownToString(new Map([['a', 1]])); // Ok('{}')
153
+ * unknownToString(new Set([1, 2, 3])); // Ok('{}')
154
+ * ```
155
+ *
156
+ * @example Integration with Result type
157
+ * ```typescript
158
+ * import { Result, pipe } from '../functional';
159
+ *
160
+ * // Chain with other Result operations
161
+ * function processUserInput(input: unknown): Result<string, Error> {
162
+ * return pipe(input)
163
+ * .map(val => unknownToString(val))
164
+ * .map(Result.flatten) // Flatten Result<Result<string, Error>, never>
165
+ * .map(str => Result.map(str, s => s.trim()))
166
+ * .map(Result.flatten)
167
+ * .map(str => Result.flatMap(str, s =>
168
+ * s.length > 0
169
+ * ? Result.ok(s)
170
+ * : Result.err(new Error('Empty string'))
171
+ * ))
172
+ * .value;
173
+ * }
174
+ * ```
175
+ *
176
+ * @see Result - For error handling pattern used by this function
177
+ * @see JSON.stringify - Underlying serialization for objects
178
+ */
179
+ export const unknownToString = (
180
+ value: unknown,
181
+ options?: Partial<Readonly<{ prettyPrintObject: boolean }>>,
182
+ ): Result<string, Error> => {
183
+ switch (typeof value) {
184
+ case 'string':
185
+ return Result.ok(value);
186
+
187
+ case 'number':
188
+ case 'bigint':
189
+ case 'boolean':
190
+ case 'symbol':
191
+ case 'function':
192
+ return Result.ok(value.toString());
193
+
194
+ case 'object':
195
+ if (!isNonNullish(value)) {
196
+ return Result.ok('null');
197
+ }
198
+ try {
199
+ const stringified =
200
+ options?.prettyPrintObject === true
201
+ ? JSON.stringify(value, undefined, 2)
202
+ : JSON.stringify(value);
203
+ return Result.ok(stringified);
204
+ } catch (error) {
205
+ return Result.err(
206
+ error instanceof Error
207
+ ? error
208
+ : new Error('Failed to stringify object'),
209
+ );
210
+ }
211
+
212
+ case 'undefined':
213
+ return Result.ok('undefined');
214
+ }
215
+ };
@@ -0,0 +1,114 @@
1
+ import { Result } from '../functional/index.mjs';
2
+ import { unknownToString } from './unknown-to-string.mjs';
3
+
4
+ describe('unknownToString', () => {
5
+ test('string', () => {
6
+ const result = unknownToString('aaaaa');
7
+ expect(Result.isOk(result)).toBe(true);
8
+ if (Result.isOk(result)) {
9
+ expect(result.value).toBe('aaaaa');
10
+ }
11
+ expect(JSON.stringify('aaaaa')).toBe('"aaaaa"');
12
+ });
13
+
14
+ test('number', () => {
15
+ const result = unknownToString(1);
16
+ expect(Result.isOk(result)).toBe(true);
17
+ if (Result.isOk(result)) {
18
+ expect(result.value).toBe('1');
19
+ }
20
+ expect(JSON.stringify(1)).toBe('1');
21
+ });
22
+
23
+ test('boolean', () => {
24
+ const result = unknownToString(true);
25
+ expect(Result.isOk(result)).toBe(true);
26
+ if (Result.isOk(result)) {
27
+ expect(result.value).toBe('true');
28
+ }
29
+ expect(JSON.stringify(true)).toBe('true');
30
+ });
31
+
32
+ test('symbol', () => {
33
+ const result = unknownToString(Symbol('sym'));
34
+ expect(Result.isOk(result)).toBe(true);
35
+ if (Result.isOk(result)) {
36
+ expect(result.value).toBe('Symbol(sym)');
37
+ }
38
+ expect(JSON.stringify(Symbol('sym'))).toBeUndefined();
39
+ });
40
+
41
+ test('function', () => {
42
+ const result = unknownToString(() => 0);
43
+ expect(Result.isOk(result)).toBe(true);
44
+ if (Result.isOk(result)) {
45
+ expect(result.value).toBe('() => 0');
46
+ }
47
+ expect(JSON.stringify(() => 0)).toBeUndefined();
48
+ });
49
+
50
+ test('undefined', () => {
51
+ const result = unknownToString(undefined);
52
+ expect(Result.isOk(result)).toBe(true);
53
+ if (Result.isOk(result)) {
54
+ expect(result.value).toBe('undefined');
55
+ }
56
+ expect(JSON.stringify(undefined)).toBeUndefined();
57
+ });
58
+
59
+ test('null', () => {
60
+ const result = unknownToString(null);
61
+ expect(Result.isOk(result)).toBe(true);
62
+ if (Result.isOk(result)) {
63
+ expect(result.value).toBe('null');
64
+ }
65
+ expect(JSON.stringify(null)).toBe('null');
66
+ });
67
+
68
+ test('object', () => {
69
+ const result = unknownToString({ a: { b: 1 } });
70
+ expect(Result.isOk(result)).toBe(true);
71
+ if (Result.isOk(result)) {
72
+ expect(result.value).toBe('{"a":{"b":1}}');
73
+ }
74
+ expect(JSON.stringify({ a: { b: 1 } })).toBe('{"a":{"b":1}}');
75
+ });
76
+
77
+ test('object(prettyPrint=true)', () => {
78
+ const result = unknownToString(
79
+ { a: { b: 1 } },
80
+ { prettyPrintObject: true },
81
+ );
82
+ expect(Result.isOk(result)).toBe(true);
83
+ if (Result.isOk(result)) {
84
+ expect(result.value).toBe(
85
+ [
86
+ //
87
+ `{`,
88
+ ` "a": {`,
89
+ ` "b": 1`,
90
+ ` }`,
91
+ `}`,
92
+ ].join('\n'),
93
+ );
94
+ }
95
+ });
96
+
97
+ test('circular reference returns error', () => {
98
+ const circular: { a: number; self?: unknown } = { a: 1 };
99
+ circular.self = circular;
100
+ const result = unknownToString(circular);
101
+ expect(Result.isErr(result)).toBe(true);
102
+ if (Result.isErr(result)) {
103
+ expect(result.value.message).toContain('circular');
104
+ }
105
+ });
106
+
107
+ test('BigInt value', () => {
108
+ const result = unknownToString(BigInt(123));
109
+ expect(Result.isOk(result)).toBe(true);
110
+ if (Result.isOk(result)) {
111
+ expect(result.value).toBe('123');
112
+ }
113
+ });
114
+ });