ProjectionPlan ¶
ProjectionPlan is a fluent builder API for the common case of composing multiple git sources into a single layered tree. It wraps the lower-level Workspace, Branch, Source, Mapping, and Projection classes into a concise interface.
Basic Usage ¶
const { Repo } = require('hologit');
const ProjectionPlan = require('hologit/lib/ProjectionPlan');
const repo = new Repo({ gitDir: '/path/to/.git', ref: 'HEAD' });
const plan = new ProjectionPlan(repo);
plan.addLayer('base', { url: '/path/to/base', ref: 'refs/heads/main' });
plan.addLayer('overlay', { url: '/path/to/overlay', ref: 'refs/heads/main' }, { after: ['base'] });
const treeHash = await plan.project();
API ¶
new ProjectionPlan(repo) ¶
Create a new plan. Requires a Repo instance for git object storage.
plan.addSource(name, config) ¶
Register a named source. Returns this for chaining.
config shape:
| Field | Required | Description |
|---|---|---|
url |
yes | Git URL or local filesystem path |
ref |
yes | Git ref (refs/heads/main, refs/tags/v1.0, commit hash) |
project |
no | { holobranch: 'name' } — project through a holobranch in the source |
plan.addMapping(sourceName, config) ¶
Add a mapping for a previously registered source. Returns this for chaining.
config shape:
| Field | Default | Description |
|---|---|---|
files |
['**'] |
Glob patterns for which files to include |
root |
'.' |
Subtree of the source to map from |
output |
'.' |
Target path in the output tree |
layer |
source name | Layer name for ordering |
after |
null |
Sources/layers this must come after |
before |
null |
Sources/layers this must come before |
plan.addLayer(name, sourceConfig, mappingConfig?) ¶
Convenience method that calls addSource then addMapping. Returns this for chaining.
This is the most common way to build a plan — each layer is a source that maps all its files (**) into the output tree:
plan.addLayer('skeleton', { url: skeletonRepo, ref: 'refs/heads/main' });
plan.addLayer('product', { url: productRepo, ref: 'refs/tags/v3.0' }, { after: ['skeleton'] });
plan.addLayer('site', { url: siteRepo, ref: 'refs/heads/main' }, { after: ['product'] });
plan.project(options?) ¶
Compose all layers and return the git tree hash. This is an async operation.
options:
| Field | Default | Description |
|---|---|---|
lens |
false |
Whether to apply hololens transformations |
fetch |
false |
Whether to fetch remote sources |
Layer Ordering ¶
Layers are composed in topological order based on after/before constraints. When two layers contain a file at the same path, the layer that comes later wins.
A typical pattern is to chain layers sequentially:
plan.addLayer('skeleton', { url: skeletonUrl, ref: skeletonRef });
plan.addLayer('product', { url: productUrl, ref: productRef }, { after: ['skeleton'] });
plan.addLayer('site', { url: siteUrl, ref: siteRef }, { after: ['product'] });
This ensures: skeleton files are laid down first, product files overlay them, site files overlay everything.
Additive directories ¶
Files in different directories from different layers coexist naturally. If skeleton provides config/search.config.d/people.ts and product provides config/search.config.d/sections.ts, both files appear in the output. Only files at the same path are overridden.
Full Example: Emergence-style Layer Stack ¶
const { Repo } = require('hologit');
const ProjectionPlan = require('hologit/lib/ProjectionPlan');
const { execSync } = require('child_process');
async function composeEmergenceSite({ gitDir, layers }) {
const repo = new Repo({ gitDir, ref: 'HEAD' });
const plan = new ProjectionPlan(repo);
let prevName = null;
for (const layer of layers) {
const mappingConfig = prevName ? { after: [prevName] } : {};
plan.addLayer(layer.name, {
url: layer.url,
ref: layer.ref || 'refs/heads/main'
}, mappingConfig);
prevName = layer.name;
}
return plan.project();
}
// Usage
const treeHash = await composeEmergenceSite({
gitDir: '/path/to/object-store',
layers: [
{ name: 'skeleton', url: 'https://github.com/org/skeleton', ref: 'refs/tags/v2.12.0' },
{ name: 'slate', url: 'https://github.com/org/slate', ref: 'refs/tags/v2.21.0' },
{ name: 'slate-cbl', url: 'https://github.com/org/slate-cbl', ref: 'refs/tags/v3.1.0' },
{ name: 'school-site', url: '/local/school-site', ref: 'refs/heads/main' },
]
});
// Check out the composed tree
const git = await (new Repo({ gitDir: '/path/to/object-store', ref: 'HEAD' })).getGit();
await git.readTree(treeHash);
await git.checkoutIndex({ a: true, f: true });
Relationship to Lower-Level API ¶
ProjectionPlan is syntactic sugar over the composition API. Under the hood:
- Source configs become phantom
Sourceinstances viaWorkspace({ sources: {...} }) - Mapping configs become phantom
Mappinginstances viaBranch({ mappings: {...} }) project()callsProjection.projectBranch()on the constructed branch
Use ProjectionPlan for standard layer composition. Use the lower-level API when you need:
- Custom
rootoroutputpaths per mapping - Glob filtering (
files) to include only specific file patterns from a source - Lens transformations
- Multiple branches in one workspace
- Direct access to the
TreeObjectbefore projection