API
The public TypeScript API of the Appling packages, export by export, with one usage example per package.
Every tool in Appling is built on five public packages. Use them to build your own checks, reports, editors, generators or agents. All packages are ES modules for Node 24.
| Package | Purpose |
|---|---|
@appling/lang | Grammar, parser, scoping, validation, expression typing, canonical printer |
@appling/lang/lsp | The language server services |
@appling/model | Semantic graph, edit operations, releases |
@appling/codegen | Code generation core: plans, registry, migrations |
@appling/ai | Workspace access for AI, MCP server, chat |
Language: @appling/lang
Parse and validate files with the Langium services. Install langium too, for URI and NodeFileSystem.
import { URI } from 'langium';
import { NodeFileSystem } from 'langium/node';
import { createApplingServices, print, type Model } from '@appling/lang';
const { shared } = createApplingServices(NodeFileSystem);
const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file('/path/to/sales.appling'));
await shared.workspace.DocumentBuilder.build([document], { validation: true });
console.log(document.diagnostics);
const canonical = print(document.parseResult.value as Model);
| Export | Purpose |
|---|---|
createApplingServices(context) | Creates the Langium services with Appling scoping and validation; returns { shared, Appling } |
ApplingModule, ApplingServices | The Appling service module and the services type |
buildSources(sources) | Builds in memory sources { [uri]: text } as one validated workspace; returns a Workspace ({ documents, services }) |
errorsOf(workspace) | The error messages of a built workspace |
print(root) | A parsed model in canonical form, the text appling fmt writes |
printRange(node) | The canonical text of one node in its document with the gaps around it: { start, end, text } (PrintedRange) |
modulePath(module) | The dotted path of a module, such as a.b |
projectionSourceEntity(projection) | The entity behind a projection, also when its source is a query |
ApplingScopeProvider | The scope provider; lookup(container, type, name) resolves a dotted name as a reference would |
ExpressionTyper | Types expressions: checkModel(model) fills types, targets (the element each name stands for) and problems (Problem[]) |
show(type), typeOfRef(ref) | The display name of a Type, such as Money?, and the Type a TypeRef declares |
LAYER_OF, Layer | The layer of every top level declaration type |
| AST types and guards | Model, Module, Entity, isEntity and the rest of the generated AST |
Language server: @appling/lang/lsp
import { startLanguageServer } from 'langium/lsp';
import { NodeFileSystem } from 'langium/node';
import { createApplingLspServices } from '@appling/lang/lsp';
startLanguageServer(createApplingLspServices({ connection, ...NodeFileSystem }).shared);
| Export | Purpose |
|---|---|
createApplingLspServices(context) | The Appling services with Langium’s LSP services; returns { shared, Appling } |
ApplingLspModule | The Appling specific LSP services: formatter and hover |
ApplingFormatter | Formats a whole document into canonical form |
ApplingHoverProvider | Hover with kind, qualified name, layer and doc comment |
@appling/lang/monarch exports the Monarch grammar as default. Both are described in Editors.
Graph and edits: @appling/model
Build the semantic graph of in memory sources and apply an edit:
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { URI } from 'langium';
import { buildSources, type Model } from '@appling/lang';
import { applyEdit, buildGraph, dependencies } from '@appling/model';
const uri = URI.file(resolve('models/shop.appling')).toString();
const sources = { [uri]: readFileSync('models/shop.appling', 'utf8') };
const { documents, services } = await buildSources(sources);
const graph = buildGraph(documents.map(d => d.parseResult.value as Model), services);
console.log(dependencies(graph)); // [{ from: 'sales', to: 'crm', count: 1 }]
const result = await applyEdit(sources, { op: 'add', parent: 'sales.Order', source: 'note: Text?' });
console.log(result.ok ? result.edits : result.errors);
| Export | Purpose |
|---|---|
buildGraph(models, services) | Nodes and edges of resolved models, in document order |
dependencies(graph) | Module to module dependencies with the number of references behind each |
isElement(node) | Whether an AST node becomes a graph node |
qualifiedNames(models) | A map from every element AST node to its qualified name, in document order |
parentElement(node, qns) | The nearest element containing an AST node |
Graph, GraphNode, GraphEdge, Dependency, Level, ExprData, TypeData | The graph types, see Semantic graph |
applyEdit(sources, operation, { release }) | Applies one edit operation, see Edit operations |
EditOperation, EditResult, TextEdit | The edit types |
writeRelease(root, version, graph) | Writes <root>/.appling/releases/<version>.json and returns its path; throws for an invalid or existing version |
readReleases(root), latestRelease(releases) | All releases of a workspace, and the one with the highest version |
releasesDir(root) | <root>/.appling/releases |
migrationBlockName(version) | The migration block for steps after a release: 1.0.0 gives since_v1_0_0 |
Release | { version, created, graph } |
findApplingFiles(path) | The .appling files at a path, searching folders recursively and skipping hidden folders and node_modules |
Code generation: @appling/codegen
Plan generation for a graph without running it, as appling generate --plan does:
import { describe, loadExtension, plan } from '@appling/codegen';
const extension = await loadExtension('@appling/codegen-django', '.');
const result = plan(graph, extension, 'shop');
for (const { node, step } of result.steps) console.log(node, describe(step));
console.log(result.entries.filter(e => e.status !== 'generated').map(e => e.qn));
Configuration and extensions:
| Export | Purpose |
|---|---|
readConfig(root) | <root>/appling.config.json as Config, or {} |
loadExtension(name, root) | Imports an extension package, resolved from root, and returns its default export |
Config, Target, CONFIG | The configuration types and the file name |
CodegenExtension | The extension contract: name, prerequisites, test, setup, steps, migrate |
Step, CommandStep, TemplateStep, TaskStep | The three step kinds |
MapContext, Prerequisite | What an extension gets per node, and a tool it needs |
Planning and running:
| Export | Purpose |
|---|---|
plan(graph, extension, name, options) | The ordered steps and one registry entry per node (Plan) |
describe(step) | A one line description of a step, as --plan prints it |
mapContext(graph, name, options) | The MapContext passed to extensions |
checkPrerequisites(prerequisites) | One message per missing tool |
runPlan(plan, out, log) | Runs the steps into out and writes the registry |
runSteps(steps, out, log) | Runs steps without writing a registry |
PROJECT | The node name of setup steps, (project) |
PlannedStep, Plan | A step with its node, and the result of plan |
Registry:
| Export | Purpose |
|---|---|
readRegistry(out), writeRegistry(out, registry) | <out>/.appling/tasks.json |
Registry, RegistryEntry, Missing, Status | The registry types, see Configuration |
REGISTRY | The registry path inside a project, .appling/tasks.json |
Migrations:
| Export | Purpose |
|---|---|
pendingChanges(graph, applied, stamp) | The model’s migration steps not in applied, with each element’s qualified name before and now (MigrationChange[]) |
planMigrations(graph, extension, name, options, out, changes) | Asks the extension’s migrate for the steps of every change |
runMigrations(out, plan, changes, log) | Runs them, adds the tasks to the registry and records the changes as applied |
readApplied(out) | The ids in <out>/.appling/migrations.json |
MigrationChange, ProjectFiles, APPLIED | The change type, read access to the project for extensions, and the applied file path |
How to write an extension is described in Writing extensions.
AI: @appling/ai
Work with a workspace folder the way the MCP tools do:
import { Project, queryGraph } from '@appling/ai';
const project = new Project('models');
console.log(await project.check()); // [] when valid
console.log(queryGraph(await project.graph(), { kind: 'Entity' }));
const result = await project.edit({ op: 'rename', target: 'sales.Order.total', name: 'amount' });
if (result.ok) project.write(result.sources);
Workspace:
| Export | Purpose |
|---|---|
Project | A workspace folder: sources(), check(), graph(), source(qn), edit(operation) with the latest release, write(sources), and the registry methods targets(), nextTask(target), updateTask(qn, status, note, target), runTests(target) |
queryGraph(graph, query) | What the graph_query tool returns |
MAX_ATTEMPTS | Attempts after which a registry entry stops coming back, 3 |
Sources | { [uri]: text } |
Tools and MCP:
| Export | Purpose |
|---|---|
MODEL_TOOLS, REGISTRY_TOOLS, Tools | The tool definitions: description, zod input shape and run(context, input) |
operationSchema | The zod schema of an edit operation |
ToolContext, diskContext(project), ProposalContext | Where tools read and write: the files on disk, or a working copy that collects operations |
createMcpServer(root) | An MCP server with all tools and the implement_next_task prompt |
serveHttp(root, port) | The MCP server over Streamable HTTP on 127.0.0.1; returns { url, close() } |
REGISTRY_PROMPT, CHAT_SYSTEM | The registry skill text and the chat system prompt |
Chat:
| Export | Purpose |
|---|---|
chat(root, messages, model) | One chat turn on a working copy; yields ChatEvents: text, tool, proposal, error |
modelSettings(root), DEFAULT_MODEL, ModelSettings | The ai section of appling.config.json with its defaults |
languageModel(settings) | The AI SDK model for the settings |
Proposal, Hunk, ChatEvent | The proposal and event types |
hunks(root, before, after), graphDiff(before, after) | The text and graph differences of a proposal |
The tools and the chat are described in AI tools.

