@planet-matrix/mobius-model 0.3.0 → 0.4.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 (82) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +4 -1
  3. package/dist/index.js +4 -2
  4. package/dist/index.js.map +18 -3
  5. package/package.json +3 -3
  6. package/scripts/build.ts +4 -4
  7. package/src/basic/README.md +143 -0
  8. package/src/basic/array.ts +872 -0
  9. package/src/basic/bigint.ts +114 -0
  10. package/src/basic/boolean.ts +180 -0
  11. package/src/basic/error.ts +51 -0
  12. package/src/basic/function.ts +453 -0
  13. package/src/basic/helper.ts +276 -0
  14. package/src/basic/index.ts +15 -0
  15. package/src/basic/is.ts +320 -0
  16. package/src/basic/number.ts +178 -0
  17. package/src/basic/object.ts +58 -0
  18. package/src/basic/promise.ts +464 -0
  19. package/src/basic/regexp.ts +7 -0
  20. package/src/basic/stream.ts +140 -0
  21. package/src/basic/string.ts +308 -0
  22. package/src/basic/symbol.ts +164 -0
  23. package/src/basic/temporal.ts +224 -0
  24. package/src/index.ts +2 -0
  25. package/src/type/README.md +330 -0
  26. package/src/type/array.ts +5 -0
  27. package/src/type/boolean.ts +471 -0
  28. package/src/type/class.ts +419 -0
  29. package/src/type/function.ts +1519 -0
  30. package/src/type/helper.ts +135 -0
  31. package/src/type/index.ts +14 -0
  32. package/src/type/intersection.ts +93 -0
  33. package/src/type/is.ts +247 -0
  34. package/src/type/iteration.ts +233 -0
  35. package/src/type/number.ts +732 -0
  36. package/src/type/object.ts +788 -0
  37. package/src/type/path.ts +73 -0
  38. package/src/type/string.ts +1004 -0
  39. package/src/type/tuple.ts +2424 -0
  40. package/src/type/union.ts +108 -0
  41. package/tests/unit/basic/array.spec.ts +290 -0
  42. package/tests/unit/basic/bigint.spec.ts +50 -0
  43. package/tests/unit/basic/boolean.spec.ts +74 -0
  44. package/tests/unit/basic/error.spec.ts +32 -0
  45. package/tests/unit/basic/function.spec.ts +175 -0
  46. package/tests/unit/basic/helper.spec.ts +118 -0
  47. package/tests/unit/basic/number.spec.ts +74 -0
  48. package/tests/unit/basic/object.spec.ts +15 -0
  49. package/tests/unit/basic/promise.spec.ts +232 -0
  50. package/tests/unit/basic/regexp.spec.ts +11 -0
  51. package/tests/unit/basic/stream.spec.ts +120 -0
  52. package/tests/unit/basic/string.spec.ts +74 -0
  53. package/tests/unit/basic/symbol.spec.ts +72 -0
  54. package/tests/unit/basic/temporal.spec.ts +78 -0
  55. package/dist/index.d.ts +0 -2
  56. package/dist/index.d.ts.map +0 -1
  57. package/dist/reactor/index.d.ts +0 -3
  58. package/dist/reactor/index.d.ts.map +0 -1
  59. package/dist/reactor/reactor-core/flags.d.ts +0 -99
  60. package/dist/reactor/reactor-core/flags.d.ts.map +0 -1
  61. package/dist/reactor/reactor-core/index.d.ts +0 -4
  62. package/dist/reactor/reactor-core/index.d.ts.map +0 -1
  63. package/dist/reactor/reactor-core/primitive.d.ts +0 -276
  64. package/dist/reactor/reactor-core/primitive.d.ts.map +0 -1
  65. package/dist/reactor/reactor-core/reactive-system.d.ts +0 -241
  66. package/dist/reactor/reactor-core/reactive-system.d.ts.map +0 -1
  67. package/dist/reactor/reactor-operators/branch.d.ts +0 -19
  68. package/dist/reactor/reactor-operators/branch.d.ts.map +0 -1
  69. package/dist/reactor/reactor-operators/convert.d.ts +0 -30
  70. package/dist/reactor/reactor-operators/convert.d.ts.map +0 -1
  71. package/dist/reactor/reactor-operators/create.d.ts +0 -26
  72. package/dist/reactor/reactor-operators/create.d.ts.map +0 -1
  73. package/dist/reactor/reactor-operators/filter.d.ts +0 -269
  74. package/dist/reactor/reactor-operators/filter.d.ts.map +0 -1
  75. package/dist/reactor/reactor-operators/index.d.ts +0 -8
  76. package/dist/reactor/reactor-operators/index.d.ts.map +0 -1
  77. package/dist/reactor/reactor-operators/join.d.ts +0 -48
  78. package/dist/reactor/reactor-operators/join.d.ts.map +0 -1
  79. package/dist/reactor/reactor-operators/map.d.ts +0 -165
  80. package/dist/reactor/reactor-operators/map.d.ts.map +0 -1
  81. package/dist/reactor/reactor-operators/utility.d.ts +0 -48
  82. package/dist/reactor/reactor-operators/utility.d.ts.map +0 -1
@@ -0,0 +1,140 @@
1
+ import type { ReadableStreamController, ReadableStreamReadResult } from "node:stream/web"
2
+
3
+ import { ReadableStream } from "node:stream/web"
4
+
5
+ /**
6
+ * 从数组创建一个 ReadableStream。
7
+ */
8
+ export const streamFromArray = <T>(values: T[]): ReadableStream<T> => {
9
+ const stream = new ReadableStream<T>({
10
+ start(controller): void {
11
+ for (const value of values) {
12
+ controller.enqueue(value)
13
+ }
14
+ controller.close()
15
+ },
16
+ })
17
+ return stream
18
+ }
19
+
20
+ export interface StreamConsumeInSyncMacroTaskOptions<T> {
21
+ readableStream: ReadableStream<T>
22
+ onValue?: ((chunk: T) => (void | Promise<void>)) | undefined
23
+ onDone?: (() => (void | Promise<void>)) | undefined
24
+ onError?: ((error: Error) => (void | Promise<void>)) | undefined
25
+ }
26
+ /**
27
+ * 在同步宏任务中完成 ReadableStream 的消费。
28
+ */
29
+ export const streamConsumeInSyncMacroTask = async <T>(
30
+ options: StreamConsumeInSyncMacroTaskOptions<T>,
31
+ ): Promise<void> => {
32
+ const { readableStream, onValue, onDone, onError } = options
33
+
34
+ try {
35
+ for await (const chunk of readableStream) {
36
+ await onValue?.(chunk)
37
+ }
38
+ await onDone?.()
39
+ }
40
+ catch (exception) {
41
+ console.error(`Error reading stream: ${String(exception)}`)
42
+ await onError?.(new Error(`Error reading stream: ${String(exception)}`))
43
+ }
44
+ }
45
+
46
+ export interface StreamConsumeInAsyncMacroTaskOptions<T> {
47
+ readableStream: ReadableStream<T>
48
+ onValue?: ((chunk: T) => (void | Promise<void>)) | undefined
49
+ onDone?: (() => (void | Promise<void>)) | undefined
50
+ onError?: ((error: Error) => (void | Promise<void>)) | undefined
51
+ }
52
+ /**
53
+ * 在异步宏任务中完成 ReadableStream 的消费。
54
+ */
55
+ export const streamConsumeInAsyncMacroTask = <T>(
56
+ options: StreamConsumeInAsyncMacroTaskOptions<T>,
57
+ ): void => {
58
+ const { readableStream, onValue, onDone, onError } = options
59
+
60
+ const reader = readableStream.getReader()
61
+ const read = (): void => {
62
+ setTimeout(() => {
63
+ void reader.read()
64
+ .then(async (chunk) => {
65
+ const { done, value } = chunk
66
+ if (done === true) {
67
+ await onDone?.()
68
+ return
69
+ }
70
+ await onValue?.(value)
71
+ read()
72
+ })
73
+ .catch(async (reason: unknown) => {
74
+ await onError?.(new Error(String(reason)))
75
+ })
76
+ }, 0)
77
+ }
78
+ read()
79
+ }
80
+
81
+ export interface StreamTransformInAsyncMacroTaskOptions<T, U> {
82
+ readableStream?: ReadableStream<T> | undefined
83
+ reader?: ReadableStreamDefaultReader<T> | undefined
84
+ onChunk?: ((chunk: ReadableStreamReadResult<T>, controller: ReadableStreamDefaultController<U>) => (void | Promise<void>)) | undefined
85
+ onError?: ((error: Error) => (void | Error | Promise<void | Error>)) | undefined
86
+ }
87
+ /**
88
+ * 在宏任务队列中完成 ReadableStream 到另一个 ReadableStream 的转换。
89
+ */
90
+ export const streamTransformInAsyncMacroTask = <T, U>(
91
+ options: StreamTransformInAsyncMacroTaskOptions<T, U>,
92
+ ): ReadableStream<U> => {
93
+ const { readableStream, reader, onChunk, onError } = options
94
+
95
+ if (reader === undefined && readableStream === undefined) {
96
+ throw new Error("Either readableStream or reader must be provided")
97
+ }
98
+
99
+ const preparedReader = reader ?? readableStream!.getReader()
100
+ const stream = new ReadableStream<U>({
101
+ start(controller): void {
102
+ const read = (): void => {
103
+ setTimeout(() => {
104
+ void preparedReader.read()
105
+ .then(async (chunk) => {
106
+ let shouldContinue = true
107
+ const proxyController: ReadableStreamController<U> = {
108
+ // oxlint-disable-next-line no-misused-spread
109
+ ...controller,
110
+ enqueue: (chunk: U): void => {
111
+ controller.enqueue(chunk)
112
+ shouldContinue = true
113
+ },
114
+ close: (): void => {
115
+ controller.close()
116
+ shouldContinue = false
117
+ },
118
+ error: (error: Error): void => {
119
+ controller.error(error)
120
+ shouldContinue = false
121
+ },
122
+ }
123
+ await onChunk?.(chunk, proxyController)
124
+ if (shouldContinue === true) {
125
+ read()
126
+ }
127
+ })
128
+ .catch(async (reason: unknown) => {
129
+ const error = new Error(String(reason))
130
+ const refinedError = await onError?.(error)
131
+ controller.error(refinedError ?? error)
132
+ })
133
+ }, 0)
134
+ }
135
+ read()
136
+ },
137
+ })
138
+
139
+ return stream
140
+ }
@@ -0,0 +1,308 @@
1
+ const internalRandomPacket: Record<number, string[]> = {}
2
+ /**
3
+ * Generate a random string with a fixed length and optional character set.
4
+ *
5
+ * @example
6
+ * ```
7
+ * // Expect: 8
8
+ * const example1 = stringRandom(8).length
9
+ * // Expect: true
10
+ * const example2 = stringRandom(4, "ab").split("").every(char => char === "a" || char === "b")
11
+ * ```
12
+ */
13
+ export const stringRandom = (length: number, chars?: string | undefined): string => {
14
+ let result = ""
15
+ const preparedChars = chars ?? "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
16
+
17
+ Array.from({ length }).forEach(() => {
18
+ result = result + preparedChars[Math.floor(Math.random() * preparedChars.length)]!
19
+ })
20
+
21
+ internalRandomPacket[length] = internalRandomPacket[length] ?? []
22
+ const packet = internalRandomPacket[length]
23
+ if (packet.includes(result)) {
24
+ return stringRandom(length, preparedChars)
25
+ }
26
+ else {
27
+ packet.push(result)
28
+ return result
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Convert a camelCase string into kebab-case.
34
+ *
35
+ * @example
36
+ * ```
37
+ * // Expect: "hello-world"
38
+ * const example1 = stringCamelCaseToKebabCase("helloWorld")
39
+ * // Expect: "ab2-cd"
40
+ * const example2 = stringCamelCaseToKebabCase("ab2Cd")
41
+ * ```
42
+ */
43
+ export const stringCamelCaseToKebabCase = (camelCase: string): string => {
44
+ return camelCase.replaceAll(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
45
+ }
46
+
47
+ /**
48
+ * Convert a kebab-case string into camelCase.
49
+ *
50
+ * @example
51
+ * ```
52
+ * // Expect: "helloWorld"
53
+ * const example1 = stringKebabCaseToCamelCase("hello-world")
54
+ * // Expect: "ab2Cd"
55
+ * const example2 = stringKebabCaseToCamelCase("ab2-cd")
56
+ * ```
57
+ */
58
+ export const stringKebabCaseToCamelCase = (kebabCase: string): string => {
59
+ return kebabCase.replaceAll(/-./g, x => x[1]!.toUpperCase())
60
+ }
61
+
62
+ /**
63
+ * Get a greeting based on the current local hour.
64
+ *
65
+ * @example
66
+ * ```
67
+ * // Expect: "你好" | "早上好" | "晚上好" | ...
68
+ * const example1 = stringHelloWord()
69
+ * ```
70
+ */
71
+ export const stringHelloWord = (): string => {
72
+ const currentHour = new Date().getHours()
73
+ if (currentHour < 5) {
74
+ return "凌晨好"
75
+ }
76
+ if (currentHour < 8) {
77
+ return "早上好"
78
+ }
79
+ if (currentHour < 12) {
80
+ return "上午好"
81
+ }
82
+ if (currentHour < 14) {
83
+ return "中午好"
84
+ }
85
+ if (currentHour < 17) {
86
+ return "下午好"
87
+ }
88
+ if (currentHour < 19) {
89
+ return "傍晚好"
90
+ }
91
+ if (currentHour < 24) {
92
+ return "晚上好"
93
+ }
94
+ return "你好"
95
+ }
96
+
97
+ /**
98
+ * Calculate the total unit length of a string.
99
+ *
100
+ * @example
101
+ * ```
102
+ * // Expect: 1.5
103
+ * const example1 = stringCalculateUnits("a中")
104
+ * // Expect: 2
105
+ * const example2 = stringCalculateUnits("中文")
106
+ * ```
107
+ */
108
+ export const stringCalculateUnits = (text: string): number => {
109
+ let units = 0
110
+
111
+ for (const char of text) {
112
+ if (/[\u0000-\u007F]/.test(char)) {
113
+ // Half-width character (e.g., ASCII)
114
+ units = units + 0.5
115
+ }
116
+ else {
117
+ // Full-width character (e.g., CJK)
118
+ units = units + 1
119
+ }
120
+ }
121
+
122
+ return units
123
+ }
124
+
125
+ /**
126
+ * Truncate a string by the unit limit and keep whole characters.
127
+ *
128
+ * @example
129
+ * ```
130
+ * // Expect: "a中"
131
+ * const example1 = stringTruncateByUnits("a中文", 1.5)
132
+ * // Expect: "abc"
133
+ * const example2 = stringTruncateByUnits("abc", 2)
134
+ * ```
135
+ */
136
+ export const stringTruncateByUnits = (text: string, maxUnits: number): string => {
137
+ let truncated = ""
138
+ let units = 0
139
+
140
+ for (const char of text) {
141
+ const charUnits = /[\u0000-\u007F]/.test(char) ? 0.5 : 1
142
+ if (units + charUnits > maxUnits) { break }
143
+
144
+ truncated = truncated + char
145
+ units = units + charUnits
146
+ }
147
+
148
+ return truncated
149
+ }
150
+
151
+ /**
152
+ * Slice a string by unit indices.
153
+ *
154
+ * @example
155
+ * ```
156
+ * // Expect: "a中"
157
+ * const example1 = stringSliceByUnits("a中文", 0, 1.5)
158
+ * // Expect: "文"
159
+ * const example2 = stringSliceByUnits("中文ab", 1, 2)
160
+ * ```
161
+ */
162
+ export const stringSliceByUnits = (text: string, start: number, end: number): string => {
163
+ let units = 0
164
+ let result = ""
165
+ for (const char of text) {
166
+ const charUnits = /[\u0000-\u007F]/.test(char) ? 0.5 : 1
167
+ if (units >= end) { break }
168
+
169
+ if (units >= start) {
170
+ result = result + char
171
+ }
172
+ units = units + charUnits
173
+ }
174
+
175
+ return result
176
+ }
177
+
178
+ export interface StringSplitOptions {
179
+ input: string
180
+ chunkSize: number
181
+ chunkOverlap: number
182
+ }
183
+
184
+ /**
185
+ * Split a long string into fixed-size chunks with optional overlap.
186
+ *
187
+ * @example
188
+ * ```
189
+ * // Expect: ["hello", "lo wo", "world"]
190
+ * const example1 = stringSplit({ input: "hello world", chunkSize: 5, chunkOverlap: 2 })
191
+ * ```
192
+ */
193
+ export const stringSplit = (options: StringSplitOptions): string[] => {
194
+ const { input, chunkSize = 1_500, chunkOverlap: overlap = 20 } = options
195
+ const safeChunkSize = Math.max(1, Math.trunc(chunkSize))
196
+ const safeOverlap = Math.max(0, Math.min(Math.trunc(overlap), safeChunkSize - 1))
197
+
198
+ // 去除换行符和其他特殊符号
199
+ const sanitizedInput = input.replaceAll(/[\t\n\r]/g, " ")
200
+
201
+ // 分割结果数组
202
+ const result: string[] = []
203
+
204
+ // 当前处理位置
205
+ let currentPosition = 0
206
+
207
+ // 循环处理整个输入字符串
208
+ while (currentPosition < sanitizedInput.length) {
209
+ // 计算当前片段的结束位置
210
+ const endPosition = Math.min(currentPosition + safeChunkSize, sanitizedInput.length)
211
+
212
+ // 获取当前片段
213
+ const segment = sanitizedInput.slice(currentPosition, endPosition)
214
+
215
+ // 将当前片段添加到结果数组中
216
+ result.push(segment)
217
+
218
+ // 如果已经到达文本末尾,退出循环
219
+ if (endPosition >= sanitizedInput.length) {
220
+ break
221
+ }
222
+
223
+ // 更新处理位置,有重叠部分
224
+ currentPosition = endPosition - safeOverlap
225
+ }
226
+
227
+ return result
228
+ }
229
+
230
+ /**
231
+ * Split a string into chunks while trying to keep natural line breaks.
232
+ *
233
+ * @example
234
+ * ```
235
+ * // Expect: ["a\\n", "b\\n", "c"]
236
+ * const example1 = stringSmartSplit("a\nb\nc", 2)
237
+ * ```
238
+ */
239
+ export const stringSmartSplit = (text: string, maxLength: number): string[] => {
240
+ let _text = text
241
+ if (_text.length <= maxLength) {
242
+ return [_text]
243
+ }
244
+
245
+ const conservativeMaxLength = maxLength * 0.9
246
+
247
+ const threshold = maxLength * 0.2
248
+ const texts: string[] = []
249
+ while (_text.length > conservativeMaxLength) {
250
+ texts.push(_text.slice(0, conservativeMaxLength))
251
+ _text = _text.slice(conservativeMaxLength)
252
+ }
253
+ if (_text.length > threshold) {
254
+ texts.push(_text)
255
+ }
256
+ else {
257
+ texts[texts.length - 1] = texts.at(-1)! + _text
258
+ }
259
+
260
+ if (texts.length === 1) {
261
+ return texts
262
+ }
263
+
264
+ for (let i = 1; i < texts.length; i = i + 1) {
265
+ const previousText = texts[i - 1]!
266
+ const currentText = texts[i]!
267
+ const lastIndexOfNewLineOfPreviousText = previousText.lastIndexOf("\n")
268
+ const firstIndexOfNewLineOfCurrentText = currentText.indexOf("\n")
269
+ const endOfPreviousText = previousText.slice(lastIndexOfNewLineOfPreviousText + 1)
270
+ const startOfCurrentText = currentText.slice(0, firstIndexOfNewLineOfCurrentText)
271
+ if (endOfPreviousText.length >= startOfCurrentText.length) {
272
+ const newPreviousText = previousText + startOfCurrentText
273
+ const newCurrentText = currentText.slice(firstIndexOfNewLineOfCurrentText + 1)
274
+ texts[i - 1] = newPreviousText
275
+ texts[i] = newCurrentText
276
+ }
277
+ else {
278
+ const newPreviousText = previousText.slice(0, lastIndexOfNewLineOfPreviousText)
279
+ const newCurrentText = endOfPreviousText + currentText
280
+ texts[i - 1] = newPreviousText
281
+ texts[i] = newCurrentText
282
+ }
283
+ }
284
+
285
+ const trimmedTexts = texts.map(text => text.trim())
286
+
287
+ return trimmedTexts
288
+ }
289
+
290
+ /**
291
+ * Truncate a string to a maximum length with an ellipsis.
292
+ *
293
+ * @example
294
+ * ```
295
+ * // Expect: "hello..."
296
+ * const example1 = stringTruncate("hello world", 5)
297
+ * // Expect: "hi"
298
+ * const example2 = stringTruncate("hi", 5)
299
+ * ```
300
+ */
301
+ export const stringTruncate = (str: string, n: number): string => {
302
+ if (str.length <= n) {
303
+ return str
304
+ }
305
+ else {
306
+ return `${str.slice(0, n)}...`
307
+ }
308
+ }
@@ -0,0 +1,164 @@
1
+ import { isSymbol } from "./is.ts"
2
+
3
+ /**
4
+ * Create a new unique symbol with an optional description.
5
+ *
6
+ * @example
7
+ * ```
8
+ * // Expect: true
9
+ * const example1 = typeof symbolCreateLocal("demo") === "symbol"
10
+ * // Expect: false
11
+ * const example2 = symbolCreateLocal("demo") === symbolCreateLocal("demo")
12
+ * ```
13
+ */
14
+ export const symbolCreateLocal = (description?: string): symbol => {
15
+ return Symbol(description)
16
+ }
17
+
18
+ /**
19
+ * Get or create a global symbol for the given key.
20
+ *
21
+ * @example
22
+ * ```
23
+ * // Expect: true
24
+ * const example1 = symbolCreateGlobal("demo") === Symbol.for("demo")
25
+ * // Expect: true
26
+ * const example2 = symbolCreateGlobal("demo") === symbolCreateGlobal("demo")
27
+ * ```
28
+ */
29
+ export const symbolCreateGlobal = (key: string): symbol => Symbol.for(key)
30
+
31
+ /**
32
+ * Get the key for a global symbol, or undefined if not global.
33
+ *
34
+ * @example
35
+ * ```
36
+ * // Expect: "demo"
37
+ * const example1 = symbolGetKey(Symbol.for("demo"))
38
+ * // Expect: undefined
39
+ * const example2 = symbolGetKey(Symbol("demo"))
40
+ * ```
41
+ */
42
+ export const symbolGetKey = (value: unknown): string | undefined => {
43
+ return isSymbol(value) ? Symbol.keyFor(value) : undefined
44
+ }
45
+
46
+ /**
47
+ * Check whether a symbol is from the global registry.
48
+ *
49
+ * @example
50
+ * ```
51
+ * // Expect: true
52
+ * const example1 = symbolIsGlobal(Symbol.for("demo"))
53
+ * // Expect: false
54
+ * const example2 = symbolIsGlobal(Symbol("demo"))
55
+ * ```
56
+ */
57
+ export const symbolIsGlobal = (value: unknown): value is symbol => {
58
+ return isSymbol(value) && Symbol.keyFor(value) !== undefined
59
+ }
60
+
61
+ /**
62
+ * Check whether a symbol is not from the global registry.
63
+ *
64
+ * @example
65
+ * ```
66
+ * // Expect: false
67
+ * const example1 = symbolIsLocal(Symbol.for("demo"))
68
+ * // Expect: true
69
+ * const example2 = symbolIsLocal(Symbol("demo"))
70
+ * ```
71
+ */
72
+ export const symbolIsLocal = (value: unknown): value is symbol => {
73
+ return isSymbol(value) && Symbol.keyFor(value) === undefined
74
+ }
75
+
76
+ /**
77
+ * Check whether a symbol has a description.
78
+ *
79
+ * @example
80
+ * ```
81
+ * // Expect: true
82
+ * const example1 = symbolHasDescription(Symbol("demo"))
83
+ * // Expect: false
84
+ * const example2 = symbolHasDescription(Symbol())
85
+ * ```
86
+ */
87
+ export const symbolHasDescription = (value: unknown): value is symbol => {
88
+ return isSymbol(value) && value.description !== undefined
89
+ }
90
+
91
+ /**
92
+ * Get the description of a symbol, or undefined for non-symbols.
93
+ *
94
+ * @example
95
+ * ```
96
+ * // Expect: "demo"
97
+ * const example1 = symbolGetDescription(Symbol("demo"))
98
+ * // Expect: undefined
99
+ * const example2 = symbolGetDescription(Symbol())
100
+ * ```
101
+ */
102
+ export const symbolGetDescription = (value: unknown): string | undefined => {
103
+ return isSymbol(value) ? value.description : undefined
104
+ }
105
+
106
+ /**
107
+ * Check whether a symbol is anonymous (no description).
108
+ *
109
+ * @example
110
+ * ```
111
+ * // Expect: false
112
+ * const example1 = symbolIsAnonymous(Symbol("demo"))
113
+ * // Expect: true
114
+ * const example2 = symbolIsAnonymous(Symbol())
115
+ * ```
116
+ */
117
+ export const symbolIsAnonymous = (value: unknown): value is symbol => {
118
+ return isSymbol(value) && value.description === undefined
119
+ }
120
+
121
+ const internalWellKnownSymbols = new Set<symbol>([
122
+ Symbol.asyncIterator,
123
+ Symbol.hasInstance,
124
+ Symbol.isConcatSpreadable,
125
+ Symbol.iterator,
126
+ Symbol.match,
127
+ Symbol.matchAll,
128
+ Symbol.replace,
129
+ Symbol.search,
130
+ Symbol.species,
131
+ Symbol.split,
132
+ Symbol.toPrimitive,
133
+ Symbol.toStringTag,
134
+ Symbol.unscopables,
135
+ ])
136
+ /**
137
+ * Check whether a symbol is one of the well-known symbols.
138
+ *
139
+ * @example
140
+ * ```
141
+ * // Expect: true
142
+ * const example1 = symbolIsWellKnown(Symbol.iterator)
143
+ * // Expect: false
144
+ * const example2 = symbolIsWellKnown(Symbol("iterator"))
145
+ * ```
146
+ */
147
+ export const symbolIsWellKnown = (value: unknown): value is symbol => {
148
+ return isSymbol(value) && internalWellKnownSymbols.has(value)
149
+ }
150
+
151
+ /**
152
+ * Convert a symbol to its string representation.
153
+ *
154
+ * @example
155
+ * ```
156
+ * // Expect: "Symbol(demo)"
157
+ * const example1 = symbolToString(Symbol("demo"))
158
+ * // Expect: undefined
159
+ * const example2 = symbolToString("demo")
160
+ * ```
161
+ */
162
+ export const symbolToString = (value: unknown): string | undefined => {
163
+ return isSymbol(value) ? value.toString() : undefined
164
+ }