Marketplace Adapter Interface
Marketplace adapters are the bridge between the CLI and marketplace-specific logic. They provide a standardized interface for loading manifests, recipe books, path keys, and transforming genomes.
Overview
Every marketplace must implement the MarketplaceAdapter interface:
interface MarketplaceAdapter {
// REQUIRED
loadManifest(): Promise<MarketplaceManifest> | MarketplaceManifest;
transformGenome(genome: any, options?, context?): Promise<StandardizedGenome>;
// OPTIONAL (but recommended)
loadRecipeBook?(): Promise<RecipeBook> | RecipeBook;
loadPathKeys?(): Promise<MarketplacePathKeys> | MarketplacePathKeys;
resolvePathDefaults?(context: PathResolutionContext): Promise<PathKeyValueSet> | PathKeyValueSet;
resolveModule?(moduleId: string): Promise<MarketplaceManifestModule | undefined>;
getDefaultParameters?(moduleId: string): Record<string, unknown> | undefined;
validateGenome?(genome: Genome): Promise<void> | void;
loadTemplate?(moduleId: string, templatePath: string): Promise<string | null>;
}Required Methods
loadManifest()
Loads the marketplace manifest that describes all available modules.
Returns: MarketplaceManifest
Structure:
{
version: string;
modules: {
capabilities?: MarketplaceManifestModule[];
features?: MarketplaceManifestModule[];
};
}Example:
async loadManifest() {
const raw = await readFile(manifestPath, 'utf-8');
return JSON.parse(raw);
}transformGenome()
Transforms a raw genome (V2Genome, Genome, etc.) into a StandardizedGenome ready for execution.
Parameters:
genome: Any genome format (V2Genome, Genome, capability-driven, etc.)options: Transformation options (mode, feature flags, metadata)context: Transformation context (marketplace adapters, logger, project root)
Returns: StandardizedGenome (always same format for CLI)
Example:
async transformGenome(genome, options, context) {
// Use V2GenomeHandler for V2 genomes
const handler = new context.V2GenomeHandler(
context.marketplaceAdapters,
context.logger,
context.projectRoot
);
const resolved = await handler.resolveGenome(genome, context.projectRoot);
return handler.convertLockFileToResolvedGenome(
resolved.lockFile,
genome,
context.genomeFilePath
);
}Optional Methods
loadRecipeBook()
Loads the recipe book that maps business packages to technical modules (V2).
Returns: RecipeBook
Structure:
{
packages: {
[packageName: string]: {
providers: {
[providerName: string]: {
modules: Array<{ id: string; ... }>;
targetPackage?: string;
targetApps?: string[];
};
};
};
};
}Example:
async loadRecipeBook() {
const raw = await readFile(recipeBookPath, 'utf-8');
return JSON.parse(raw);
}loadPathKeys()
Loads the path key schema for the marketplace.
Returns: MarketplacePathKeys
Structure:
{
keys: {
[key: string]: {
description?: string;
semantic?: boolean;
resolveToApps?: string[];
// ... other metadata
};
};
}Example:
async loadPathKeys() {
const raw = await readFile(pathKeysPath, 'utf-8');
return JSON.parse(raw);
}resolvePathDefaults()
Resolves default path values based on the genome.
Parameters:
context:PathResolutionContext(genome, apps, packages, etc.)
Returns: PathKeyValueSet (key-value pairs of paths)
Example:
async resolvePathDefaults(context) {
const paths = {};
// Resolve app paths
for (const app of context.apps) {
paths[`apps.${app.type}.root`] = app.package || `apps/${app.type}/`;
}
// Resolve package paths
for (const [name, path] of Object.entries(context.packages)) {
paths[`packages.${name}.root`] = path;
}
return paths;
}resolveModule()
Resolves a module entry by ID from the manifest.
Parameters:
moduleId: Module ID (e.g.,capabilities/nextjs)
Returns: MarketplaceManifestModule | undefined
Example:
async resolveModule(moduleId) {
await ensureManifestLoaded();
return moduleIndex?.get(moduleId);
}getDefaultParameters()
Returns default parameters for a module (merged before execution).
Parameters:
moduleId: Module ID
Returns: Record<string, unknown> | undefined
Example:
getDefaultParameters(moduleId) {
const module = moduleIndex?.get(moduleId);
return module?.parameters?.defaults;
}validateGenome()
Validates a genome before transformation.
Parameters:
genome: Genome to validate
Throws: Error if genome is invalid
Example:
validateGenome(genome) {
if (!genome || typeof genome !== 'object') {
throw new Error('Genome must be an object.');
}
if (!genome.project || typeof genome.project !== 'object') {
throw new Error('Genome must include a project configuration.');
}
}loadTemplate()
Loads template content from the marketplace.
Parameters:
moduleId: Module IDtemplatePath: Template file path
Returns: string | null
Example:
async loadTemplate(moduleId, templatePath) {
const module = await this.resolveModule(moduleId);
if (!module) return null;
const fullPath = path.join(marketplaceRoot, module.source.root, templatePath);
return await readFile(fullPath, 'utf-8');
}Implementation Example
Here's a complete marketplace adapter implementation:
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const manifestPath = fileURLToPath(new URL('../manifest.json', import.meta.url));
const recipeBookPath = fileURLToPath(new URL('../recipe-book.json', import.meta.url));
const pathKeysPath = fileURLToPath(new URL('../path-keys.json', import.meta.url));
const marketplaceRoot = fileURLToPath(new URL('../', import.meta.url));
let manifestCache;
let moduleIndex;
let pathKeysCache;
async function ensureManifestLoaded() {
if (manifestCache && moduleIndex) return;
const raw = await readFile(manifestPath, 'utf-8');
manifestCache = JSON.parse(raw);
moduleIndex = new Map(
flattenModules(manifestCache).map(mod => [mod.id, mod])
);
}
export const marketplaceAdapter = {
async loadManifest() {
await ensureManifestLoaded();
return manifestCache;
},
async loadRecipeBook() {
const raw = await readFile(recipeBookPath, 'utf-8');
return JSON.parse(raw);
},
async loadPathKeys() {
if (pathKeysCache) return pathKeysCache;
const raw = await readFile(pathKeysPath, 'utf-8');
pathKeysCache = JSON.parse(raw);
return pathKeysCache;
},
async resolvePathDefaults(context) {
const paths = {};
// Resolve paths based on genome
return paths;
},
async resolveModule(moduleId) {
await ensureManifestLoaded();
return moduleIndex?.get(moduleId);
},
getDefaultParameters(moduleId) {
const module = moduleIndex?.get(moduleId);
return module?.parameters?.defaults;
},
validateGenome(genome) {
if (!genome?.project) {
throw new Error('Genome must include a project configuration.');
}
},
async transformGenome(genome, options, context) {
// Use V2GenomeHandler for transformation
const handler = new context.V2GenomeHandler(
context.marketplaceAdapters,
context.logger,
context.projectRoot
);
const resolved = await handler.resolveGenome(genome, context.projectRoot);
return handler.convertLockFileToResolvedGenome(
resolved.lockFile,
genome,
context.genomeFilePath
);
}
};Marketplace Structure
A marketplace must provide:
manifest.json: Module definitionsrecipe-book.json: Package to module mappings (V2)path-keys.json: Path key definitionsadapter/index.js: Adapter implementation
Creating a Marketplace
Use the CLI command to generate marketplace structure:
architech marketplace generate <name> --type opinionatedThis creates:
- Directory structure (
capabilities/,features/,adapter/) package.jsonwith correct exportsadapter/index.jswith all required methods- Build scripts for manifest and recipe book generation
Related
- Framework Bootstrap - How frameworks are initialized
- Creating Your Marketplace - Complete guide
- CLI Architecture - Overall CLI architecture