# LikeC4 API

import { PackageManagers } from 'starlight-package-managers';

You can access and traverse your architecture model programmatically using the LikeC4 Model API.

<Aside type='note'>
API allows to query and traverse the model from DSL, but not modify or create a new one.
</Aside>

Ensure you have `likec4` in your dependencies:

<PackageManagers
    pkg="likec4"
    pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
    frame="none"
  />

## Usage

You can initiate LikeC4 API from a directory with source files or from a string with DSL source.

### From workspace

Recursively search and parse source files:

```ts
import { LikeC4 } from 'likec4'

const likec4 = await LikeC4.fromWorkspace('/path/to/workspace')
```  

Method also accepts options:

| Property         | Description                                                                                         |
| -----------------| --------------------------------------------------------------------------------------------------- |
| `printErrors`    | if model is invalid, errors are reported to the logger (default `true`) |
| <span style="text-wrap:nowrap">`throwIfInvalid`</span> | return rejected promise if model is invalid  (default `false`) |
| `logger`         | Whenever to use `default` (console), `vite` logger  or your custom implementation <br/> Disable with `false`  |
| `graphviz`       | `wasm` (default) or `binary` - use local binaries of Graphviz ("dot") or bundled WASM    |
| `watch`          | Whether to watch for changes in the workspace. (default `false`)  |
| `mcp`            | Whether to start MCP server.<br/> - `false` - do not start MCP server (default) <br/> - `"stdio"` - use stdio transport,<br/> -  `{"port": number}` - use http transport on specified port |

:::tip
Take a look at [Validate your model](/guides/validate-your-model) for examples of how to enforce custom rules on your model with the LikeC4 API
:::

### From source

Parse from the string:

```ts
import { LikeC4 } from "likec4"

const likec4 = await LikeC4.fromSource(`
  specification {
    element system
    element user
  }
  model {
    customer = user 'Customer'
    cloud = system 'System'
  }
  views {
    view index {
      include *
    }
  }
`)
```

### Dispose

If you initialized LikeC4 with `watch` mode or enabled MCP server, you should dispose it:

```ts
import { LikeC4 } from 'likec4'

const likec4 = await LikeC4.fromWorkspace('/path/to/workspace', {
  watch: true,
  mcp: { port: 33335 },
})

// Cleanup resources
await likec4.dispose()
```

LikeC4 is automatically disposed with `await using` declaration:

```ts
import { LikeC4 } from 'likec4'

async function() {
  await using likec4 = await LikeC4.fromWorkspace('/path/to/workspace', {
    watch: true,
    mcp: { port: 33335 },
  })

  // ...
  // likec4 is disposed automatically
}
```

## API

When the model is initialized, you can use the following methods to query and traverse it.

Two types of model (with similar API):

- **LikeC4Model.Computed** - includes computed views (from predicates), fast, synchronous, enough to traverse but not ready for rendering.
- **LikeC4Model.Layouted** - extends computed model with layout data (dimensions, positions), that is needed for rendering.

:::tip
Low-level API is available from [`@likec4/core`](https://github.com/likec4/likec4/blob/main/packages/core/README.md).
:::

### Example

```ts
import { LikeC4 } from "likec4"

const likec4 = await LikeC4.fromSource(`....`)

// Validation errors
console.log(likec4.getErrors())

// Traverse the model
const model = likec4.computedModel()

// Get elements of some kind
const elements = model.elementsOfKind('kind1')

// Use where operator to filter elements:
//  kind is 'kind1' and (tag is 'tag2' or tag is not 'tag3')
const elements = model.elementsWhere({
  and: [
    { kind: 'kind1' },
    {
      or: [
        { tag: 'tag2' },
        {
          tag: {
            neq: 'tag3',
          },
        },
      ],
    },
  ],
})

// Get views that include the element
model
  .element('cloud.backend.api')
  .views()

// Get source elements of incoming relationships (filter by tags)
model
  .element('cloud.backend.api')
  .incoming() // relationships incoming to the element
  .filter(r => r.isTagged('http')) // filter by tags
  .map(r => r.source) // get source elements

```  

To get layouted model:

```ts
import { LikeC4 } from "likec4"

const likec4 = await LikeC4.fromSource(`....`)

const model = await likec4.layoutedModel()

const diagram = model.view('index')

// Working with metadata (including array values)
const element = model.element('cloud.backend.api')

// Get all metadata
const allMetadata = element.getMetadata()
console.log(allMetadata)
// Output: { version: '3.2.1', tags: ['backend', 'gateway'], regions: ['us-east-1', 'eu-west-1'] }

// Get specific metadata field (could be string or string[])
const tags = element.getMetadata('tags')
if (Array.isArray(tags)) {
  console.log(`Element has ${tags.length} tags: ${tags.join(', ')}`)
} else if (tags) {
  console.log(`Element has single tag: ${tags}`)
}

// Filter elements by array metadata values
const elementsWithBackendTag = model.elements().filter(el => {
  const tags = el.getMetadata('tags')
  return Array.isArray(tags) 
    ? tags.includes('backend')
    : tags === 'backend'
})

```  

### LikeC4Model

:::tip
It is possible to generate Typed API from your model, see [Code generation](/tooling/code-generation/model/)
:::

Model API provides methods to query and traverse the whole model.

```ts
interface LikeC4Model {
  /**
   * Returns the root elements of the model.
   */
  roots(): Element[];
  /**
   * Returns all elements in the model.
   */
  elements(): Element[];
  /**
   * Returns a specific element by its FQN.
   */
  element(id: Fqn): Element;
  /**
   * Returns all relationships in the model.
   */
  relationships(): Relationship[];
  /**
   * Returns a specific relationship by its ID.
   */
  relationship(id: RelationID): Relationship;
  /**
   * Returns all views in the model.
   */
  views(): ReadonlyArray<LikeC4ViewModel>;
  /**
   * Returns a specific view by its ID.
   */
  view(viewId: ViewID): LikeC4ViewModel;
  /**
   * Returns the parent element of given element.
   * @see ancestors
   */
  parent(element: ElementOrFqn): Element | null;
  /**
   * Get all children of the element (only direct children),
   * @see descendants
   */
  children(element: ElementOrFqn): Element[];
  /**
   * Get all sibling (i.e. same parent)
   */
  siblings(element: ElementOrFqn): Element[];
  /**
   * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
   * (from closest to root)
   */
  ancestors(element: ElementOrFqn): Element[];
  /**
   * Get all descendant elements (i.e. children, children’s children, etc.)
   */
  descendants(element: ElementOrFqn): Element[];
  /**
   * Incoming relationships to the element and its descendants
   * @see incomers
   */
  incoming(element: ElementOrFqn, filter?: 'all' | 'direct' | 'to-descendants'): Relationship[];
  /**
   * Source elements of incoming relationships
   */
  incomers(element: ElementOrFqn, filter?: 'all' | 'direct' | 'to-descendants'): Element[];
  /**
   * Outgoing relationships from the element and its descendants
   * @see outgoers
   */
  outgoing(element: ElementOrFqn, filter?: 'all' | 'direct' | 'from-descendants'): Relationship[];
  /**
   * Target elements of outgoing relationships
   */
  outgoers(element: ElementOrFqn, filter?: 'all' | 'direct' | 'from-descendants'): Element[];
}
```

Check sources for methods - [LikeC4Model](https://github.com/likec4/likec4/blob/main/packages/core/src/model/LikeC4Model.ts)

### Working with Element Metadata

Elements can have metadata with both single string values and string arrays. The API provides convenient methods to access and work with this metadata:

```ts
import { LikeC4 } from "likec4"

const likec4 = await LikeC4.fromSource(`
  specification {
    element service
    element application
  }
  model {
    api = service 'API Gateway' {
      metadata {
        version '3.2.1'
        maintainer 'Platform Team'
        tags ['backend', 'gateway', 'microservice']
        regions ['us-east-1', 'eu-west-1']
        critical true
      }
    }
    
    frontend = application 'Frontend' {
      metadata {
        framework 'React'
        features ['auth', 'dashboard', 'reports']
        team_members ['alice', 'bob', 'carol']
        release_branch 'main'
      }
    }
  }
`)

const model = likec4.computedModel()

// Get element and access metadata
const api = model.element('api')

// Check if element has any metadata
if (api.hasMetadata()) {
  console.log('API has metadata')
  
  // Get all metadata as an object
  const metadata = api.getMetadata()
  console.log('All metadata:', metadata)
  
  // Get specific metadata fields
  const version = api.getMetadata('version')        // string: '3.2.1'
  const tags = api.getMetadata('tags')              // string[]: ['backend', 'gateway', 'microservice']
  const regions = api.getMetadata('regions')        // string[]: ['us-east-1', 'eu-west-1']
  
  // Handle array metadata values
  if (Array.isArray(tags)) {
    console.log(`API has ${tags.length} tags:`)
    tags.forEach(tag => console.log(`  - ${tag}`))
    
    // Check if specific value exists in array
    if (tags.includes('backend')) {
      console.log('API is tagged as backend service')
    }
  }
  
  // Handle mixed metadata types
  const handleMetadataValue = (key: string, value: string | string[] | undefined) => {
    if (Array.isArray(value)) {
      return `${key}: [${value.join(', ')}]`
    } else if (value) {
      return `${key}: ${value}`
    }
    return `${key}: undefined`
  }
  
  console.log(handleMetadataValue('version', version))
  console.log(handleMetadataValue('tags', tags))
  console.log(handleMetadataValue('regions', regions))
}

// Advanced filtering using metadata
const backendServices = model.elements()
  .filter(element => {
    const tags = element.getMetadata('tags')
    return Array.isArray(tags) ? tags.includes('backend') : tags === 'backend'
  })

const multiRegionServices = model.elements()
  .filter(element => {
    const regions = element.getMetadata('regions')
    return Array.isArray(regions) && regions.length > 1
  })

// Group elements by metadata values
const elementsByFramework = new Map<string, typeof model.elements>()
for (const element of model.elements()) {
  const framework = element.getMetadata('framework')
  if (typeof framework === 'string') {
    if (!elementsByFramework.has(framework)) {
      elementsByFramework.set(framework, [])
    }
    elementsByFramework.get(framework)!.push(element)
  }
}

// Collect all unique tags from all elements
const allTags = new Set<string>()
for (const element of model.elements()) {
  const tags = element.getMetadata('tags')
  if (Array.isArray(tags)) {
    tags.forEach(tag => allTags.add(tag))
  } else if (typeof tags === 'string') {
    allTags.add(tags)
  }
}
console.log('All unique tags:', Array.from(allTags).sort())
```

:::note[Metadata Merging with Extends]
When using [`extend`](/dsl/extend/) to add metadata to elements, duplicate metadata keys are automatically merged:

- String + String = Array (if different values)
- String + Array = Array (merged)
- Array + Array = Array (merged)
- Duplicate values are automatically de-duplicated
- Single-value arrays are converted back to strings

This allows you to progressively build up metadata across multiple files. See the [extend documentation](/dsl/extend/#metadata-merging) for more details.
:::

### LikeC4DeploymentModel

API provides methods to query and traverse deployment model.

```ts
import { LikeC4 } from "likec4"

const likec4 = await LikeC4.fromSource(`....`)
const model = likec4.computedModel()

// Get deployment model
const deployment = model.deployment

// Get elements of some kind
for (const instance of deployment.instancesOf('cloud.backend.api')) {
  // ...
}
```

### LikeC4ViewModel

View model API provides methods to query and traverse elements and relationships that are included in the view.

```ts
import { LikeC4 } from "likec4"

const likec4 = await LikeC4.fromSource(`....`)
const model = likec4.computedModel()

for (const view of model.views()) {
  if (view.isDynamicView()) {
    // ...
  }
}
```


## Model Builder

Type-safe builder available from `@likec4/core/builder` (and `likec4/model/builder`).  
Builder can be used to create model programmatically and supports two styles:

<Tabs>
  <Tab label='Chain'>
  ```ts
  import { Builder } from "@likec4/core/builder"

  const m = Builder
    .specification({
      elements: {
        actor: {
          style: {
            shape: 'person',
          },
        },
        system: {},
        component: {},
      },
      relationships: {
        likes: {},
      },
      tags: ['tag1', 'tag2', 'tag1'],
    })
    .model(({ actor, system, component, relTo, rel }, _) =>
      _(
        actor('alice'),
        actor('bob'),
        rel('alice', 'bob', {
          tags: ['tag1'], // you get code completion for tags
          kind: 'likes',  // code completion for kind
        }),
        system('cloud', { tags: ['tag1', 'tag2'] }).with(
          component('backend').with(
            component('api'),
            component('db'),
            // code completion for relationships
            rel('cloud.backend.api', 'cloud.backend.db')
          ),
          component('frontend').with(
            relTo('cloud.backend.api')
          ),
        ),
      )
    )    
    .views(({ view, viewOf, $include, $style }, _) =>
      _(
        view('index', 'Index').with(
          $include('cloud.*'),
        ),
        viewOf('ui', 'cloud.ui').with(
          // code completion for predicates
          $include('* -> cloud.**'),
          $style('cloud.ui', { color: 'red' }),
        ),
      )
    )
    .toLikeC4Model()
  ```
  </Tab>

  <Tab label='Composition'>

  ```ts
  import { Builder } from "@likec4/core/builder"

  // Get composition functions for given specification
  const {
    model: {
      model,
      actor,
      system,
      component,
      rel,
      relTo,
    },
    views: {
      view,
      viewOf,
      views,
      $include,
      $style,
    },      
    builder,
  } = Builder.forSpecification({
    elements: {
      actor: {
        style: {
          shape: 'person',
        },
      },
      system: {},
      component: {},
    },
    relationships: {
      likes: {},
    },
    tags: ['tag1', 'tag2', 'tag1'],
  })

  const b1 = builder.with(
    model(
      actor('alice'),
      actor('bob'),
      rel('alice', 'bob', {
        tags: ['tag1'],
        kind: 'likes',
      }),
      system('cloud', { tags: ['tag1', 'tag2'] }).with(
        component('backend').with(
          component('api'),
          component('db'),
          rel('cloud.backend.api', 'cloud.backend.db')
        ),
        component('frontend').with(
          relTo('cloud.backend.api')
        ),
      ),
    )
  )

  const b2 = b1.with(
    views(
      view('index', 'Index').with(
        $include('cloud.*'),
      ),
      viewOf('ui', 'cloud.ui').with(
        $include('* -> cloud.**'),
        $style('cloud.ui', { color: 'red' }),
      ),
    )
  )
  .toLikeC4Model()
  ```
  </Tab>
</Tabs>

You can mix both styles, depending on your preference and use cases.  

:::tip
Check unit tests in our repository for examples:
- <a href="https://github.com/likec4/likec4/blob/main/packages/core/src/builder/Builder-style1.spec.ts">Builder-style1</a>
- <a href="https://github.com/likec4/likec4/blob/main/packages/core/src/builder/Builder-style2.spec.ts">Builder-style2</a>
:::