{"slug": "module-of-the-week-rcmap", "title": "Module of the Week - RcMap", "summary": "Effect's Module of the Week post details RcMap, a data structure in the Effect TypeScript library that combines a keyed map with reference counting to share and automatically clean up resources such as Git repository checkouts. The post demonstrates RcMap through a hypothetical \"Roast My Repo\" app in which two AI reviewers share a single checkout, with each reviewer incrementing a reference count on connect and decrementing it on release so the directory is deleted when the count reaches zero. RcMap.make requires a lookup function that receives a Repository key and returns an Effect that acquires the checkout, and the Git service's checkout method requires a Scope.", "body_md": "On Effect Office Hours, people often ask me to “roast” their Effect codebases. This takes some nerve on my part, given that my own GitHub profile is public.\n\nRather than give anyone a reason to look at my code, let’s outsource the roasting. We’ll build “Roast My Repo”, a hypothetical app with AI reviewers trained to bring out your inner imposter syndrome.\n\n## \n\nTo gain access to your code, the app will clone your repository into a temporary folder. We will start with two AI reviewers: one examines your dependency graph and the other reviews your code. Both will need to access the same repository, so they will share a checkout instead of cloning the repository twice.\n\nBut if both review agents are sharing one checkout, how do we know when we can delete it?\n\nWe can track this with a count. Each reviewer adds one when it starts using the checkout and subtracts one when it’s done. When the count reaches zero, nobody needs the files anymore, so we can safely remove the directory.\n\nTry connecting and disconnecting the reviewers below to see this in action.\n\nClick a reviewer to connect or release.\n\nNo checkout. Reference count: 0. Connect a reviewer to create a checkout.\n\nThis is called **reference counting**.\n\n## \n\nSo far, our reviewers have all been reviewing the same repository. But what happens when a user submits a different one?\n\nSuppose we’re reviewing `acme/api` and `acme/website`. They need separate checkouts, each with its own reference count.\n\nSelect **Reviewer A**, then click `acme/api` to connect them. Do the same with **Reviewer B** and `acme/website`. Now click `acme/website` again to release Reviewer B’s checkout. Watch it disappear while Reviewer A’s checkout stays available.\n\nSelect a reviewer, then click a repository to connect or release.\n\nReviewer A selected. Neither repository has a checkout.\n\nWe can keep track of these checkouts in a map, using the repository URL and commit as the key. Each entry holds a checkout and its reference count. Reviewers requesting the same key share that checkout. When its count reaches zero, we delete the checkout and remove the entry.\n\nSelect `acme/api` beneath **Reviewer A** to add it to the map. Select it beneath **Reviewer B** too: the reference count increases, but there’s still only one entry. Deselect it beneath both reviewers and watch the entry disappear.\n\nUsing a map allows us to access shared resources by key. Reference counting lets us automatically clean them up when they’re no longer in use. Effect’s `RcMap` combines these two ideas into a single data structure.\n\nLet’s use `RcMap` to implement the checkout sharing we’ve just seen.\n\n## \n\n### \n\nLet’s put our checkouts behind a `Git` service. Its `checkout` method takes a repository URL and desired commit and returns the path to a local checkout.\n\nWe’ll group the URL and commit in a `Repository` class. This is what we’ll pass to `checkout` and use as the key in our map:\n\n`RcMap` compares keys using Effect’s equality rules. Two separately created `Repository` values with the same URL and commit count as the same key. In Effect v4, plain objects also have structural equality by default, so we could use those instead, but `Data.Class` is useful here for grouping the fields and getter together.\n\nInside the `Git` service, we create the map using the `RcMap.make` constructor. We must define a `lookup` function, which will receives a `Repository` and returns an `Effect` that acquires the checkout.\n\nFor this example, we’ll create a temporary directory and log where the clone would go. A full implementation would run Git before returning the path.\n\n### \n\nConstructing the map doesn’t create any directories yet. When `checkout` is invoked, it delegates to `RcMap.get`. If the key isn’t in the map, `RcMap` runs the `lookup` function which will create the checkout and insert an entry into the map. Otherwise, the reviewer shares the existing checkout.\n\nNotice that the `checkout` method provided by the `Git` service requires a `Scope`.\n\nThat’s how `RcMap` tracks how long each reviewer needs the files. When the `Scope` associated with a given call to `checkout` closes, the reference count for that map entry is decremented. When no references remain, the temporary directory is removed.\n\n### \n\nLet’s run two reviews against the same repository. Each review gets its own scope, so it can release the checkout as soon as it’s done.\n\n`Effect.all` runs both reviews concurrently against the same repository, so if one is still acquiring the checkout, the other waits for it. Once it’s ready, both receive the same directory path.\n\nPlacing `Effect.scoped` after `Effect.flatMap` keeps the scope open while the review runs. When the review finishes, the scope closes and releases its reference to the checkout.\n\nSay the dependency review finishes first. Its scope closes, taking the reference count from two to one. The code review carries on using the directory. When that finishes, the count reaches zero and `RcMap` removes the directory. Neither review needs to know what the other is doing.\n\n### \n\nBoth reviews finish, their associated `Scope` s are closed, and the checkout directory is deleted from the file system. A second later, another reviewer asks for the same repository. Now we have to re-clone the entire repository again.\n\nThis is pretty inefficient.\n\nLuckily, `RcMap` provides us with the ability to keep entries around for a little while before cleaning them up. Let’s set the map’s `idleTimeToLive` to five seconds, giving another review a chance to reuse the same checkout before it’s deleted:\n\nNow, when the reference count for a given checkout reaches zero, the checkout sticks around for five more seconds before being cleaned up.\n\nClose all three scopes below to start the countdown, then click **Get checkout** before it reaches zero. You’ll get the same directory back. Close the new scope and wait five seconds to see it removed.\n\n### \n\nSuppose we only want to run an optional review if there’s already a checkout for that repository. If there isn’t, we’ll skip the review. Calling `get` won’t work here: it creates a checkout when the key is missing. We need a way to request an existing entry without creating one.\n\n`RcMap.getOption` does exactly that. It returns `Some(path)` for an existing checkout, or `None` if the key is missing. If the checkout is still being acquired, it waits for the path rather than returning `None`.\n\nTry clicking **Get existing** below with an empty map. Then, click **Get checkout** followed by **Get existing** and observe the difference.\n\n### \n\nSuppose we added a scheduler that assigns reviews to a pool of AI workers. The dependency review has finished, but the code review is still queued, waiting for a free worker. Nothing is using the checkout, so its idle countdown has started. We’d like to keep those files around for the queued review rather than clone the repository again.\n\nThe scheduler can call `RcMap.touch` periodically to reset the idle countdown while the review waits for a worker:\n\n### \n\nKeeping idle checkouts around saves us from cloning them again, but they still take up disk space. We can limit how many entries the map holds by setting the map’s `capacity`:\n\nAttempting to add a new entry to a map that is at capacity will fail with a `Cause.ExceededCapacityError`.\n\nThe map below has a capacity of two entries. Request `acme/api` and `acme/website`, then try `acme/docs`. The third request fails, but requesting `acme/api` again still works because it shares an existing entry.\n\nWith a capacity set, `RcMap.get` can fail with `Cause.ExceededCapacityError` when a new entry would exceed the limit. Our `GitService.checkout` signature needs to include that error unless we map it to our existing `GitError`:\n\nI’d keep `ExceededCapacityError` in the signature so callers can decide what to do when the map is full, such as re-queueing the review or retrying after a delay.\n\n### \n\nSuppose a review runs a tool that modifies files in the checkout. The repository URL and commit haven’t changed, but the files on disk have. We’d like the next review to get a clean copy.\n\nInside our `Git` service, we can remove the entry from the map with `RcMap.invalidate`:\n\nThe next time `get` is invoked for that repository, the map’s `lookup` function will be called and a new checkout will be created.\n\nBut what happens to reviews that were using the checkout we invalidated? Let’s take a look.\n\nClick **Invalidate** below, then **Get checkout**. Notice what happens to the scopes referencing the invalidated checkout.\n\nInvalidated map entries keep their references, so in our case the old checkout will remain on the file system until the last `Scope` associated with it is closed.\n\n## \n\nEffect’s reference-counted data structures are exceptionally useful in situations where you have multiple consumers that all need shared access to the same scoped resource.\n\nWe focused on `RcMap` in this post, which manages shared resources by key. Effect also provides `RcRef` for when you only need to share a single resource.\n\nWe hope you enjoyed this edition of Module of the Week! Until the next one - Happy Effecting!\n\n[Previous This Week in Effect - 136](https://effect.website/blog/this-week-in-effect/136)", "url": "https://wpnews.pro/news/module-of-the-week-rcmap", "canonical_source": "https://effect.website/blog/module-of-the-week/rcmap/", "published_at": "2026-09-21 00:00:00+00:00", "updated_at": "2026-09-22 21:25:32.150849+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools"], "entities": ["Effect", "RcMap", "Git", "Roast My Repo", "Effect Office Hours"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/module-of-the-week-rcmap", "markdown": "https://wpnews.pro/news/module-of-the-week-rcmap.md", "text": "https://wpnews.pro/news/module-of-the-week-rcmap.txt", "jsonld": "https://wpnews.pro/news/module-of-the-week-rcmap.jsonld"}}