likec4-mutator
v0.4.2
Published
Programmatic mutation of LikeC4 .c4 files
Maintainers
Readme
likec4-mutator
TypeScript library + CLI for programmatic mutation of LikeC4 .c4 files. Parses .c4 via @likec4/language-server (Langium), mutates through CST-position-based text replacement, serializes back preserving formatting.
Library API
import { LikeC4Mutator } from 'likec4-mutator';
const mutator = LikeC4Mutator.fromFiles({
'model.c4': `
specification {
element service
element database
}
model {
app = service 'My App'
}
views {
view index {
include *
}
}
`});
// Query
const el = mutator.getElement('app');
const all = mutator.listElements({ kind: 'service' });
const rels = mutator.getRelationships({ sourceFqn: 'app' });
const source = mutator.getElementSource('app');
const spec = mutator.getSpecification();
// Add element (all properties)
mutator.addElement('app', {
name: 'db',
kind: 'database',
title: 'PostgreSQL',
summary: 'Primary data store',
description: 'Main database for user and transaction data',
technology: 'PostgreSQL 16',
tags: ['internal', 'critical'],
links: [
{ url: 'https://docs.example.com/db', label: 'DB Docs' },
{ url: 'https://github.com/example/db' },
],
style: {
shape: 'cylinder',
color: 'blue',
icon: 'tech:postgresql',
opacity: '80%',
border: 'dashed',
},
metadata: {
owner: 'platform-team',
env: 'production',
keywords: ['internal', 'critical'], // array values are supported
},
});
// Add relationship (all properties)
mutator.addRelationship('app', 'app.db', 'reads/writes', {
description: 'Queries the main database',
technology: 'JDBC',
tags: ['internal'],
links: [{ url: 'https://docs.example.com/db', label: 'DB Docs' }],
metadata: { sla: '99.9%' },
style: { line: 'dashed', color: 'red', head: 'diamond', tail: 'none' },
});
// Add view
mutator.addView({
id: 'appView',
type: 'element', // 'element' | 'dynamic' | 'deployment'
target: 'app',
title: 'App Overview',
includes: ['*'],
});
// Update element (only specified fields are changed)
mutator.updateElement('app.db', {
title: 'Updated Title', // REPLACE
summary: 'Updated summary', // REPLACE
description: 'Updated description', // REPLACE
technology: 'PostgreSQL 17', // REPLACE
tags: ['deprecated'], // REPLACE (v0.4.0 BREAKING — was append). Empty [] clears all tags.
links: [{ url: 'https://new.link' }], // REPLACE. Empty [] clears all links.
metadata: { // MERGE + null-deletion
team: 'backend', // upsert (string)
keywords: ['core', 'critical'], // upsert (array)
deprecated: null, // delete the key
},
style: { color: 'red' }, // MERGE per-field (v0.4.0 BREAKING — was full replace)
});
// Update an existing relationship
mutator.updateRelationship(
{ source: 'app', target: 'app.db' },
{
label: 'persists',
description: 'Reads and writes user data',
technology: 'JDBC',
tags: ['internal'], // REPLACE
links: [{ url: 'https://docs.example.com/db' }], // REPLACE
metadata: { sla: '99.9%', owner: null }, // MERGE + null-delete
style: { line: 'dashed', color: 'red' }, // MERGE per-field
},
);
// Remove
mutator.removeElement('app.db');
mutator.removeRelationship('app', 'app.db');
// Validate & serialize
const errors = mutator.validate(); // [] = valid
const files = mutator.serialize(); // { 'model.c4': '...' }CLI
validate
Parse and validate all .c4 files. Exits with code 0 if valid, 1 if errors found.
likec4-mutator validate --dir ./c4list-elements
List elements with optional filtering by kind or parent FQN.
likec4-mutator list-elements --dir ./c4
likec4-mutator list-elements --dir ./c4 --kind service
likec4-mutator list-elements --dir ./c4 --parent app
likec4-mutator list-elements --dir ./c4 --jsonget-element
Get a single element by fully qualified name (FQN).
# Human-readable output
likec4-mutator get-element --dir ./c4 --fqn app.api
# JSON output
likec4-mutator get-element --dir ./c4 --fqn app.api --json
# Raw DSL source text
likec4-mutator get-element --dir ./c4 --fqn app.api --sourceadd-element
Add a new element as a child of an existing element. Supports summary and tags directly via CLI flags. For full property support (links, metadata, style), use the apply command with JSON.
likec4-mutator add-element --dir ./c4 \
--parent app \
--kind service \
--id myApi \
--title 'My API' \
--summary 'Short label shown on diagrams' \
--description 'REST API' \
--technology 'TypeScript' \
--tags internal,backend \
--output ./outadd-relationship
Add a relationship between two elements. For full property support (description, technology, tags, style), use the apply command with JSON.
likec4-mutator add-relationship --dir ./c4 \
--source app.myApi \
--target app.db \
--label 'reads/writes' \
--output ./outupdate-element
Update properties of an existing element. Only the flags you provide are changed; all other properties are left intact.
likec4-mutator update-element --dir ./c4 --fqn app.api \
--title 'New Title' \
--description 'New description' \
--technology 'Go' \
--summary 'Updated summary' \
--tags deprecated,legacy \
--output ./outremove-element
Remove an element (and its entire body block) from the model.
likec4-mutator remove-element --dir ./c4 --fqn app.api --output ./outremove-relationship
Remove a relationship matched by exact source and target identifiers.
likec4-mutator remove-relationship --dir ./c4 --source app.api --target app.db --output ./outapply
Apply a batch of mutations from a JSON file. This is the most powerful command — supports all 6 operations with all properties.
# Write to a separate output directory
likec4-mutator apply --dir ./c4 --mutations mutations.json --output ./out
# Overwrite source files in-place
likec4-mutator apply --dir ./c4 --mutations mutations.json --in-placeBatch mutations format
The JSON file contains a mutations array. Each mutation has an op field and operation-specific parameters.
addElement
Adds a new element inside a parent. All fields except op, parent, kind, id, title are optional.
{
"op": "addElement",
"parent": "app",
"kind": "service",
"id": "newApi",
"title": "New API",
"summary": "Short description for diagram",
"description": "Detailed description",
"technology": "TypeScript / Express",
"tags": ["internal", "backend"],
"links": [
{ "url": "https://github.com/example/api", "label": "Repository" },
{ "url": "https://docs.example.com/api" }
],
"style": {
"shape": "rectangle",
"color": "blue",
"icon": "tech:typescript",
"opacity": "80%",
"border": "solid",
"multiple": false,
"size": "md",
"padding": "sm",
"textSize": "md",
"iconPosition": "top",
"iconColor": "blue",
"iconSize": "sm"
},
"metadata": {
"owner": "platform-team",
"version": "v2",
"keywords": ["internal", "critical"]
}
}updateElement
Updates properties of an existing element. Only specified fields are changed.
| Field | Semantics |
| --- | --- |
| title, summary, description, technology | REPLACE |
| tags | REPLACE (v0.4.0 BREAKING — was append). [] clears all tags. |
| links | REPLACE. [] clears all links. |
| metadata | MERGE. Map a key to null to delete it; map to a string or string[] to upsert. Keys absent from the patch are preserved verbatim (including original array formatting). |
| style | MERGE per-field (v0.4.0 BREAKING — was full replace). Pass a complete style object to reproduce the old replace-all behaviour. |
{
"op": "updateElement",
"fqn": "app.api",
"title": "Updated Title",
"description": "Updated description",
"technology": "Go / Fiber",
"tags": ["deprecated"],
"links": [{ "url": "https://migration.example.com", "label": "Migration Guide" }],
"metadata": {
"team": "backend",
"keywords": ["internal", "critical"],
"deprecatedKey": null
},
"style": { "color": "red", "border": "dashed" }
}updateRelationship
Updates fields on an existing relationship. Required: op, source, target, plus at least one update field. When more than one relation matches source/target, supply matchKind and/or matchTitle to disambiguate.
| Field | Semantics |
| --- | --- |
| label, description, technology | REPLACE |
| tags | REPLACE. [] clears all tags. |
| links | REPLACE. [] clears all links. |
| metadata | MERGE with null-deletion (same as updateElement). |
| style | MERGE per-field. |
{
"op": "updateRelationship",
"source": "app.api",
"target": "app.db",
"matchTitle": "reads/writes",
"label": "persists",
"description": "Reads and writes user data",
"technology": "JDBC",
"tags": ["internal"],
"links": [{ "url": "https://docs.example.com/db" }],
"metadata": { "sla": "99.9%", "deprecatedOwner": null },
"style": { "line": "dashed", "color": "red" }
}removeElement
Removes an element and all its children.
{
"op": "removeElement",
"fqn": "app.oldService"
}addRelationship
Adds a relationship between two elements. All fields except op, source, target are optional.
{
"op": "addRelationship",
"source": "app.api",
"target": "app.db",
"label": "reads/writes",
"description": "Queries user data",
"technology": "JDBC",
"tags": ["internal"],
"links": [{ "url": "https://docs.example.com/db", "label": "DB Docs" }],
"metadata": { "sla": "99.9%", "owners": ["platform", "data"] },
"style": {
"line": "dashed",
"color": "red",
"head": "diamond",
"tail": "none"
}
}removeRelationship
Removes a relationship between two elements (matched by exact source and target FQN).
{
"op": "removeRelationship",
"source": "app.api",
"target": "app.oldService"
}addView
Adds a new view. Type can be element, dynamic, or deployment.
{
"op": "addView",
"id": "apiView",
"type": "element",
"target": "app.api",
"title": "API Overview"
}Full batch example
{
"mutations": [
{ "op": "addElement", "parent": "app", "kind": "service", "id": "gateway", "title": "API Gateway", "description": "Routes requests", "technology": "nginx" },
{ "op": "addRelationship", "source": "app.gateway", "target": "app.api", "label": "proxies" },
{ "op": "updateElement", "fqn": "app.api", "technology": "Go / Fiber" },
{ "op": "updateRelationship", "source": "app.gateway", "target": "app.api", "matchTitle": "proxies", "metadata": { "sla": "99.9%" } },
{ "op": "addView", "id": "gatewayView", "type": "element", "target": "app.gateway", "title": "Gateway" },
{ "op": "removeRelationship", "source": "app.api", "target": "app.legacy" },
{ "op": "removeElement", "fqn": "app.legacy" }
]
}Supported properties reference
Element properties
| Property | Type | addElement | updateElement | DSL syntax |
|----------|------|:----------:|:-------------:|------------|
| title | string | yes | yes (replace) | = kind 'Title' |
| summary | string | yes | yes (replace) | summary 'text' |
| description | string | yes | yes (replace) | description 'text' |
| technology | string | yes | yes (replace) | technology 'text' |
| tags | string[] | yes | yes (replace) | #tagname |
| links | {url, label?}[] | yes | yes (replace) | link url 'label' |
| style | ElementStyle | yes | yes (merge per-field) | style { shape ... } |
| metadata | Record<string, string \| string[]> | yes | yes (merge, null deletes a key) | metadata { key 'val' } or metadata { key ['v1', 'v2'] } |
Element style properties
| Property | Values |
|----------|--------|
| shape | rectangle, person, browser, mobile, cylinder, storage, queue, bucket, document |
| color | primary, secondary, muted, slate, blue, indigo, sky, red, gray, green, amber |
| icon | Library icons (tech:postgresql, aws:lambda) or URL |
| opacity | Percentage (40%, 100%) |
| border | solid, dashed, dotted, none |
| multiple | true, false |
| size | xs, sm, md, lg, xl |
| padding | xs, sm, md, lg, xl |
| textSize | xs, sm, md, lg, xl |
| iconPosition | left, right, top, bottom |
| iconColor | Theme colors |
| iconSize | xs, sm, md, lg, xl |
Relationship properties
| Property | Type | addRelationship | updateRelationship | DSL syntax |
|----------|------|:---------------:|:------------------:|------------|
| label | string | yes | yes (replace) | -> target 'label' |
| description | string | yes | yes (replace) | description 'text' |
| technology | string | yes | yes (replace) | technology 'text' |
| tags | string[] | yes | yes (replace) | #tagname |
| links | {url, label?}[] | yes | yes (replace) | link url 'label' |
| metadata | Record<string, string \| string[]> | yes | yes (merge, null deletes a key) | metadata { key 'val' } or metadata { key ['v1', 'v2'] } |
| style | RelationshipStyle | yes | yes (merge per-field) | style { line ... } |
Relationship style properties
| Property | Values | |----------|--------| | line | solid, dashed, dotted | | color | Theme colors | | head | normal, onormal, diamond, odiamond, crow, open, vee, dot, odot, none | | tail | Same as head |
How it works
- Parse —
.c4source → AST + CST via@likec4/language-server(Langium standalone, no LSP) - Query — find elements by FQN, walk AST, build index
- Mutate — compute text edits from CST positions (
$cstNode.offset/end) - Apply — splice edits into source text (end → start to preserve offsets)
- Reparse — verify result is valid
.c4
Build
npm install
npm run build # tsup → ESM + TypeScript declarations
npm test # vitest
npm run lint # tsc --noEmitLicense
MIT
