Adapter Interface Specification
Defines the contract for data source adapters.
Interface
/**
* Data source adapter interface.
* Adapters abstract how content files are accessed,
* allowing different storage backends (Git, filesystem, API, etc.).
*/
interface DataAdapter {
/** Unique adapter identifier */
readonly id: string;
/** Human-readable name */
readonly name: string;
/**
* Initialize the data source.
* For GitAdapter: clones the repository.
* For FilesystemAdapter: validates the path exists.
* Called once before any read operations.
*/
init(config: AdapterConfig): Promise<void>;
/**
* Read a single file's contents as a UTF-8 string.
* @param relativePath - Path relative to content root (e.g., '.works/works.yml', 'data/foo/foo.yml')
* @throws If file does not exist
*/
readFile(relativePath: string): Promise<string>;
/**
* List all files (not directories) in a directory.
* @param relativeDir - Directory relative to content root (e.g., 'data', 'comparisons')
* @returns Array of file names only (not full paths)
*/
listFiles(relativeDir: string): Promise<string[]>;
/**
* List all immediate subdirectories in a directory.
* @param relativeDir - Directory relative to content root
* @returns Array of directory names only
*/
listDirectories(relativeDir: string): Promise<string[]>;
/**
* Check if a file or directory exists.
* @param relativePath - Path relative to content root
*/
exists(relativePath: string): Promise<boolean>;
/**
* Get the absolute filesystem path to the content root.
* Used when tools need direct filesystem access (e.g., Pagefind indexing).
*/
getContentPath(): string;
/**
* Pull latest changes from the remote data source.
* For GitAdapter: git fetch + fast-forward merge via isomorphic-git.
* For FilesystemAdapter: checks file mtimes for changes.
* @returns `true` if content changed, `false` if already up-to-date
*/
refresh(): Promise<boolean>;
/**
* Get the current HEAD reference for cheap change detection.
* For GitAdapter: returns the current commit SHA.
* For FilesystemAdapter: returns a hash of file mtimes.
* @returns Reference string, or null if unavailable
*/
getHeadRef(): Promise<string | null>;
}
Configuration
interface AdapterConfig {
/** Repository URL (git adapter) or API base URL (API adapter) */
repository?: string;
/** Authentication token */
token?: string;
/** Git branch name (default: 'main') */
branch?: string;
/** Local filesystem path (filesystem adapter) */
localPath?: string;
/** Clone depth for git (default: 1 for shallow clone) */
cloneDepth?: number;
/** Additional adapter-specific options */
[key: string]: unknown;
}
Built-in Adapters
GitAdapter (@ever-works/adapters)
Clones a Git repository using isomorphic-git (pure JS, no git binary required).
Config:
repository— GitHub HTTPS URL (required)token— GitHub PAT for private repos (optional)branch— Branch name (default:'main')cloneDepth— Shallow clone depth (default:1)
Behavior:
- Uses
isomorphic-gitfor all git operations (clone, fetch, pull, resolveRef) - Pure JavaScript — works in any Node.js environment including serverless
- Sets up HTTP auth header for private repos
- Skips clone if
.content/.gitalready exists (idempotent) refresh()fetches + fast-forward merges from remotegetHeadRef()returns current commit SHA viaresolveRef- Content path:
<project-root>/.content/
FilesystemAdapter (@ever-works/adapters)
Reads from a local directory. For development only.
Config:
localPath— Absolute path to content directory (required)
Behavior:
- Validates path exists on
init() - All reads are direct filesystem operations
- No git operations
Adapter Factory
import { GitAdapter, FilesystemAdapter, type DataAdapter } from '@ever-works/adapters';
/**
* Create the appropriate data adapter based on environment.
* Priority: CONTENT_PATH (filesystem) > DATA_REPOSITORY (git)
*/
function createAdapter(): DataAdapter {
if (process.env.CONTENT_PATH) {
return new FilesystemAdapter();
}
if (process.env.DATA_REPOSITORY) {
return new GitAdapter();
}
throw new Error(
'No data source configured. Set DATA_REPOSITORY or CONTENT_PATH.'
);
}
Security
- Path traversal protection: All adapters must validate that
relativePathdoes not escape the content root (no..components, no absolute paths). - Token handling: Tokens are never logged or included in error messages.
- Read-only: Adapters are read-only at build time. No write operations.