odetovibe
v1.0.0
Published
CLI and library for generating TypeScript code from declarative YAML codascon domain schemas
Maintainers
Readme
odetovibe
YAML-to-TypeScript code generation for codascon domains.
Odetovibe reads a declarative YAML schema describing your codascon domain — entity types, operations, and dispatch strategies — and generates the TypeScript scaffolding that enforces the protocol. On re-generation, existing method bodies, business logic and user comments are preserved.
Odetovibe is self-hosted. Its own ETL pipeline (Extract → Transform → Load) is implemented using the codascon protocol, described in YAML, and its TypeScript scaffolding is generated by odetovibe itself.
Install
CLI (global):
npm install -g odetovibeLibrary (local):
npm install odetovibe
# or
pnpm add odetovibeCLI
# See all options
npx odetovibe --help
# Generate TypeScript scaffolding (default: merge mode — preserves existing method bodies)
npx odetovibe schema.yaml --outDir src/
# Unconditional overwrite — replaces all generated files
npx odetovibe schema.yaml --outDir src/ --overwrite
# Strict mode — writes .ode.ts alongside the original on conflict instead of overwriting
npx odetovibe schema.yaml --outDir src/ --no-overwriteIf installed globally, replace npx odetovibe with odetovibe.
Write modes:
| Mode | Flag | Behaviour |
| ----------- | ---------------- | ------------------------------------------------------------------------------------------ |
| merge | (default) | Reconciles generated structure with existing code — method bodies and JSDoc are preserved |
| overwrite | --overwrite | Unconditionally replaces every file |
| strict | --no-overwrite | Like merge, but writes filename.ode.ts instead of overwriting on any structural conflict |
YAML Schema
Define your domain declaratively:
namespace: campus
domainTypes:
Principal: {}
Student:
resolverName: resolveStudent
Professor:
resolverName: resolveProfessor
Building: {}
AccessResult: {}
commands:
AccessBuildingCommand:
commandName: accessBuilding
baseType: Principal
objectType: Building
returnType: AccessResult
subjectUnion: [Student, Professor]
dispatch:
Student: BasicAccess
Professor: FullAccess
templates:
AccessTemplate:
isParameterized: false
strategies:
BasicAccess: {}
FullAccess: {}Key rules:
domainTypeswithresolverNamebecome Subject classes; without become interfaces- Every entry in
subjectUnionmust appear indispatch dispatchvalues are plain Strategy names, looked up across the Command'stemplates- All Templates generate as abstract classes, regardless of whether
strategiesis empty or not - Template
subjectSubsetmust be a subset of the parent Command'ssubjectUnion - Strategy
subjectSubsetmust be a subset of the parent Template'ssubjectSubset; invalid (error) when the parent Template is not parameterized
See src/schema.ts for the full schema type definitions and validation rules.
Library API
Odetovibe exposes a three-phase ETL pipeline:
import { Project } from "ts-morph";
import { parseYaml, validateYaml, emitAst, writeFiles } from "odetovibe";
// Extract: parse YAML and validate against schema rules
const configIndex = parseYaml("campus.yaml");
const { valid, validationResults } = validateYaml(configIndex);
if (!valid) {
for (const validationResult of validationResults) {
for (const error of validationResult.errors)
console.error(`[${error.entryKey}] ${error.rule}: ${error.message}`);
}
process.exit(1);
}
// Transform: emit TypeScript AST into an in-memory ts-morph Project
const project = new Project({ useInMemoryFileSystem: true });
emitAst(configIndex, { configIndex, project });
// Load: write SourceFiles to disk (merge preserves existing method bodies)
const results = await writeFiles(project, { targetDir: "./src", mode: "merge" });
for (const fileResult of results) {
if (fileResult.compileErrors) {
console.error("compile errors →", fileResult.path);
for (const e of fileResult.compileErrors) console.error(" ", e);
} else if (fileResult.conflicted) {
console.warn("conflict →", fileResult.path);
} else {
console.log(fileResult.created ? "created" : "updated", fileResult.path);
}
}What Gets Generated
For each YAML entry, odetovibe emits:
| YAML entry | Generated output |
| ----------------------------------- | ------------------------------------------------------------------------------------- |
| domainType with resolverName | class SubjectName extends Subject |
| domainType without resolverName | interface TypeName |
| command | class CommandName extends Command<...> with typed resolver method stubs |
| template | abstract class TemplateName implements Template<...> with a concrete execute stub |
| strategy | class StrategyName extends TemplateName |
All classes are fully typed — generic parameters, resolver method signatures, hook properties — so the compiler enforces the codascon protocol from the first run.
License
MIT
