返回 AiToEarn
configPath.ts
1 import type { ConfigPath, ConfigPathSegment, ConfigValue } from '../types'
2
3 export function isRecord(value: unknown): value is Record<string, unknown> {
4 return typeof value === 'object' && value !== null && !Array.isArray(value)
5 }
6
7 export function getValueAtPath(source: unknown, path: ConfigPath) {
8 return path.reduce<unknown>((current, segment) => {
9 if (current == null)
10 return undefined
11
12 if (Array.isArray(current) && typeof segment === 'number')
13 return current[segment]
14
15 if (isRecord(current) && typeof segment === 'string')
16 return current[segment]
17
18 return undefined
19 }, source)
20 }
21
22 function cloneContainer(current: unknown, nextSegment: ConfigPathSegment) {
23 if (Array.isArray(current))
24 return [...current]
25 if (isRecord(current))
26 return { ...current }
27 return typeof nextSegment === 'number' ? [] : {}
28 }
29
30 function setValueRecursive(current: unknown, path: ConfigPath, value: ConfigValue, index: number): ConfigValue {
31 const segment = path[index]
32 if (segment === undefined)
33 return value
34
35 const nextSegment = path[index + 1]
36 const container = cloneContainer(current, nextSegment)
37
38 if (Array.isArray(container) && typeof segment === 'number') {
39 container[segment] = setValueRecursive(container[segment], path, value, index + 1)
40 return container
41 }
42
43 if (isRecord(container) && typeof segment === 'string') {
44 container[segment] = setValueRecursive(container[segment], path, value, index + 1)
45 return container
46 }
47
48 return container
49 }
50
51 export function setValueAtPath<T extends Record<string, unknown>>(source: T, path: ConfigPath, value: ConfigValue): T {
52 const nextValue = setValueRecursive(source, path, value, 0)
53 return isRecord(nextValue) ? nextValue as T : source
54 }
55
56 export function joinPath(path: ConfigPath) {
57 return path.join('.')
58 }
59
60 export function formatConfigKey(key: string) {
61 return key
62 .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
63 .replace(/[-_]/g, ' ')
64 .replace(/\b\w/g, char => char.toUpperCase())
65 }
66
67 export function createEmptyValue(sample: unknown): ConfigValue {
68 if (typeof sample === 'number')
69 return 0
70 if (typeof sample === 'boolean')
71 return false
72 if (typeof sample === 'string')
73 return ''
74 if (Array.isArray(sample))
75 return []
76 if (isRecord(sample)) {
77 return Object.fromEntries(Object.entries(sample).map(([key, value]): [string, ConfigValue] => [key, createEmptyValue(value)]))
78 }
79 return ''
80 }
81
82 export function stableStringify(value: unknown) {
83 return JSON.stringify(value, (_key, nestedValue: unknown) => {
84 if (!isRecord(nestedValue))
85 return nestedValue
86
87 return Object.keys(nestedValue)
88 .sort()
89 .reduce<Record<string, unknown>>((result, key) => {
90 result[key] = nestedValue[key]
91 return result
92 }, {})
93 })
94 }
95
95 lines TYPESCRIPT