mobx-bonsai
Version:
A fast lightweight alternative to MobX-State-Tree + Y.js two-way binding
58 lines (52 loc) • 2.32 kB
text/typescript
import { failure } from "../error/failure"
import { getGlobalConfig } from "../globalConfig"
import { isPlainObject, isPrimitive } from "../plainTypes/checks"
import { getNodeTypeAndKey, NodeKeyValue } from "./nodeTypeKey/nodeType"
/**
* Deeply substitutes node keys in a data structure with new keys generated by a provided function.
*
* This function recursively traverses the input data structure and replaces all node keys with new ones
* according to the provided key generator function. The function creates a deep clone of the original
* data structure, ensuring no references to the original remain.
*
* Node keys are unique identifiers that are used internally to track and identify nodes in the data tree.
* Substituting these keys is useful when cloning nodes to ensure each node has a unique identity.
*
* The function handles:
* - Primitives: returned as-is
* - Arrays: recursively processes each element
* - Plain objects: identifies node keys using `getNodeTypeAndKey` and substitutes them
*
* @template T - The type of the input value
* @param value - The value to process (primitive, array, or plain object)
* @param newNodeKeyGenerator - Function to generate new node keys from old ones.
* Defaults to the global key generator from configuration.
* @returns A deep clone of the original value with all node keys substituted
*
* @throws Will throw an error if encountering a value type that isn't a primitive, array, or plain object
*/
export function substituteNodeKeys<T>(
value: T,
newNodeKeyGenerator: (oldKey: NodeKeyValue) => NodeKeyValue = getGlobalConfig().keyGenerator
): T {
if (isPrimitive(value)) {
return value
}
if (Array.isArray(value)) {
return value.map((v) => substituteNodeKeys(v, newNodeKeyGenerator)) as T
}
if (isPlainObject(value)) {
const typeAndKey = getNodeTypeAndKey(value)
const keyProp = typeAndKey.type && "key" in typeAndKey.type ? typeAndKey.type.key : undefined
const newValue: any = {}
for (const key in value) {
if (key === keyProp) {
newValue[key] = newNodeKeyGenerator(value[key])
} else {
newValue[key] = substituteNodeKeys(value[key], newNodeKeyGenerator)
}
}
return newValue
}
throw failure("unsupported value type")
}