--- title: "hotpatchR Overview" author: "David Munoz Tord" date: "2026-04-15" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{hotpatchR Overview} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` # Introduction This vignette explains the `hotpatchR` design and why runtime namespace patching is the right tool for legacy hotfix workflows. ## The legacy package lockdown problem A loaded R package lives in a locked namespace. Internal functions are resolved from inside the package bubble, so calling a hidden helper from the global environment does not change the package's internal behavior. This is the namespace trap that makes legacy hotfixing difficult: a visible exported function may still invoke a broken internal helper even after you have sourced a fixed version elsewhere. ## Why the global workaround fails The usual workaround is: - identify the broken internal function - find every package caller that depends on it - copy the broken internals into a hotfix script - source the script into the global environment - run tests and hope the package resolves the new bindings That workflow is brittle because a bug in one hidden helper can force you to patch many callers, even when only one implementation needs to change. ## hotpatchR philosophy Instead of pulling functions out into the global environment, `hotpatchR` performs surgical edits inside the package namespace. That means: - fix only the broken internal function - exported callers automatically resolve to the updated implementation - the package remains loaded and otherwise unchanged ## Basic workflow Before using the package, install it from CRAN or GitHub: ```r install.packages("hotpatchR") # or, for the development version: # remotes::install_github("munoztd0/hotpatchR") ``` The package includes a real example of this pattern with an exported parent function and an internal child helper. ```{r example} library(hotpatchR) baseline <- dummy_parent_func("test") print(baseline) #> "Parent output -> I am the BROKEN child. Input: test" inject_patch( pkg = "hotpatchR", patch_list = list( dummy_child_func = function(x) { paste("I am the FIXED child! Input:", x) } ) ) patched_result <- dummy_parent_func("test") print(patched_result) #> "Parent output -> I am the FIXED child! Input: test" ``` ## How inject_patch works `inject_patch()` 1. identifies the target namespace or environment 2. unlocks the binding for the named object 3. assigns the replacement function into that environment 4. re-locks the binding Because the replacement function can be defined with the package namespace as its parent, it still has access to the package's internal helpers. ## Rolling back a patch If you need to restore the original binding, `undo_patch()` reverses the previous change. ```{r undo-example} undo_patch(pkg = "hotpatchR", names = "dummy_child_func") restored_result <- dummy_parent_func("test") print(restored_result) #> "Parent output -> I am the BROKEN child. Input: test" ``` ## Hotfix scripts `apply_hotfix_file()` is a convenience wrapper for scripted hotfix application. A compatible hotfix file should define: - `pkg` (optional, if not passed explicitly) - `patch_list`, a named list of replacement functions Example hotfix file for this package: ```r # pkg <- "hotpatchR" patch_list <- list( dummy_child_func = function(x) { paste("I am the FIXED child! Input:", x) } ) ``` Then apply it with: ```r apply_hotfix_file("dev/hotpatchR_hotfix.R") ``` ## Next steps The current package is focused on the core runtime patching path. Future enhancements may include patch comparison, dependency scanning, and CI-friendly test wrappers that explicitly preserve the patched namespace during test execution.