/** Strict dotted-path get/set/delete helpers for secrets migration targets. */ import { isDeepStrictEqual } from "node:util"; import { isBlockedObjectKey } from "../infra/prototype-keys.js"; import { parseConfigPathArrayIndex } from "../shared/path-array-index.js"; import { isRecord } from "./shared.js"; function looksLikeArrayIndexSegment(segment: string): boolean { return /^\d+$/.test(segment); } function parseArrayIndexSegment(segment: string): number | undefined { return parseConfigPathArrayIndex(segment); } function requireArrayIndexSegment(segment: string, pathLabel: string): number { const index = parseArrayIndexSegment(segment); if (index === undefined) { throw new Error(`Invalid array index segment "${segment}" at ${pathLabel}.`); } return index; } function expectedContainer(nextSegment: string): "array" | "object" { return looksLikeArrayIndexSegment(nextSegment) ? "array" : "object"; } function assertSafeMutationPath(segments: string[]): void { if (segments.length === 0) { throw new Error("Target path is empty."); } const blockedSegment = segments.find(isBlockedObjectKey); if (blockedSegment) { throw new Error(`Refusing to mutate prototype-polluting path segment "${blockedSegment}".`); } } function parseArrayLeafTarget( cursor: unknown, leaf: string, segments: string[], ): { array: unknown[]; index: number } | null { if (!Array.isArray(cursor)) { return null; } return { array: cursor, index: requireArrayIndexSegment(leaf, segments.join(".")) }; } function traverseToLeafParent(params: { root: unknown; segments: string[]; requireExistingSegment: boolean; }): unknown { assertSafeMutationPath(params.segments); let cursor: unknown = params.root; for (let index = 0; index < params.segments.length - 1; index += 1) { const segment = params.segments[index] ?? ""; if (Array.isArray(cursor)) { const arrayIndex = requireArrayIndexSegment(segment, params.segments.join(".")); // Existing-path mutations must fail before the leaf so callers do not create partial config. if (params.requireExistingSegment && (arrayIndex < 0 || arrayIndex >= cursor.length)) { throw new Error( `Path segment does not exist at ${params.segments.slice(0, index + 1).join(".")}.`, ); } cursor = cursor[arrayIndex]; continue; } if (!isRecord(cursor)) { throw new Error( `Invalid path shape at ${params.segments.slice(0, index).join(".") || ""}.`, ); } if (params.requireExistingSegment && !Object.hasOwn(cursor, segment)) { throw new Error( `Path segment does not exist at ${params.segments.slice(0, index + 1).join(".")}.`, ); } cursor = cursor[segment]; } return cursor; } /** * Reads a config path from object/array containers. * Missing containers, invalid array indexes, and scalar parents resolve to undefined. */ export function getPath(root: unknown, segments: string[]): unknown { if (segments.length === 0) { return undefined; } let cursor: unknown = root; for (const segment of segments) { if (Array.isArray(cursor)) { const arrayIndex = parseArrayIndexSegment(segment); if (arrayIndex === undefined) { return undefined; } cursor = cursor[arrayIndex]; continue; } if (!isRecord(cursor)) { return undefined; } cursor = cursor[segment]; } return cursor; } /** * Sets a config path, creating missing object or array containers from the next path segment. * Existing non-container parents fail so callers cannot silently change config shape. */ export function setPathCreateStrict( root: Record, segments: string[], value: unknown, ): boolean { assertSafeMutationPath(segments); let cursor: unknown = root; let changed = false; for (let index = 0; index < segments.length - 1; index += 1) { const segment = segments[index] ?? ""; const nextSegment = segments[index + 1] ?? ""; const needs = expectedContainer(nextSegment); // Numeric next segments create arrays; named next segments create objects. // This keeps registry wildcard paths and config array paths materialized consistently. if (Array.isArray(cursor)) { const arrayIndex = requireArrayIndexSegment(segment, segments.join(".")); const existing = cursor[arrayIndex]; if (existing === undefined || existing === null) { cursor[arrayIndex] = needs === "array" ? [] : {}; changed = true; } else if (needs === "array" ? !Array.isArray(existing) : !isRecord(existing)) { throw new Error(`Invalid path shape at ${segments.slice(0, index + 1).join(".")}.`); } cursor = cursor[arrayIndex]; continue; } if (!isRecord(cursor)) { throw new Error(`Invalid path shape at ${segments.slice(0, index).join(".") || ""}.`); } const existing = cursor[segment]; if (existing === undefined || existing === null) { cursor[segment] = needs === "array" ? [] : {}; changed = true; } else if (needs === "array" ? !Array.isArray(existing) : !isRecord(existing)) { throw new Error(`Invalid path shape at ${segments.slice(0, index + 1).join(".")}.`); } cursor = cursor[segment]; } const leaf = segments[segments.length - 1] ?? ""; const arrayTarget = parseArrayLeafTarget(cursor, leaf, segments); if (arrayTarget) { if (!isDeepStrictEqual(arrayTarget.array[arrayTarget.index], value)) { arrayTarget.array[arrayTarget.index] = value; changed = true; } return changed; } if (!isRecord(cursor)) { throw new Error(`Invalid path shape at ${segments.slice(0, -1).join(".") || ""}.`); } if (!isDeepStrictEqual(cursor[leaf], value)) { cursor[leaf] = value; changed = true; } return changed; } /** * Sets an existing config path and throws if any parent or leaf segment is missing. * Used by runtime resolution paths that must only replace values proven by source discovery. */ export function setPathExistingStrict( root: Record, segments: string[], value: unknown, ): boolean { const cursor = traverseToLeafParent({ root, segments, requireExistingSegment: true }); const leaf = segments[segments.length - 1] ?? ""; const arrayTarget = parseArrayLeafTarget(cursor, leaf, segments); if (arrayTarget) { if (arrayTarget.index < 0 || arrayTarget.index >= arrayTarget.array.length) { throw new Error(`Path segment does not exist at ${segments.join(".")}.`); } if (!isDeepStrictEqual(arrayTarget.array[arrayTarget.index], value)) { arrayTarget.array[arrayTarget.index] = value; return true; } return false; } if (!isRecord(cursor)) { throw new Error(`Invalid path shape at ${segments.slice(0, -1).join(".") || ""}.`); } if (!Object.hasOwn(cursor, leaf)) { throw new Error(`Path segment does not exist at ${segments.join(".")}.`); } if (!isDeepStrictEqual(cursor[leaf], value)) { cursor[leaf] = value; return true; } return false; } /** * Deletes an existing config path, returning whether anything was removed. * Array deletes compact with splice; object deletes remove only the concrete leaf key. */ export function deletePathStrict(root: Record, segments: string[]): boolean { const cursor = traverseToLeafParent({ root, segments, requireExistingSegment: false }); const leaf = segments[segments.length - 1] ?? ""; const arrayTarget = parseArrayLeafTarget(cursor, leaf, segments); if (arrayTarget) { if (arrayTarget.index < 0 || arrayTarget.index >= arrayTarget.array.length) { return false; } // Arrays are compacted to preserve predictable index semantics. arrayTarget.array.splice(arrayTarget.index, 1); return true; } if (!isRecord(cursor)) { throw new Error(`Invalid path shape at ${segments.slice(0, -1).join(".") || ""}.`); } if (!Object.hasOwn(cursor, leaf)) { return false; } delete cursor[leaf]; return true; }