@archlex/language-service
v0.2.2
Published
Editor-neutral language intelligence for ArchLex source
Downloads
246
Maintainers
Readme
@archlex/language-service
Editor-neutral language intelligence for ArchLex source code.
Overview
@archlex/language-service provides context-aware code completion for ArchLex diagrams. It analyzes ArchLex source, understands the current cursor position, and suggests relevant completions based on:
- Catalog metadata - Service names, relationships, and containment rules from provider catalogs
- Search terms - Human-readable names and descriptions for fuzzy matching
- Document structure - Current provider, scope hierarchy, and declared symbols
- Grammar context - Directive values, resource kinds, relationship types, and scope keywords
This package is DOM-neutral and works in both browser and Node.js environments without dependencies on Monaco, VSCode, or any specific editor framework.
Installation
pnpm add @archlex/language-serviceUsage
Basic Example (Non-Monaco)
import { createCompletionEngine, analyzeLanguageDocument } from "@archlex/language-service";
import { createArchLex, awsProvider, gcpProvider, k8sProvider } from "@archlex/core";
// Create a completion engine with catalog metadata
const archlex = createArchLex({ providers: [awsProvider(), gcpProvider(), k8sProvider()] });
const catalog = archlex.getCatalog();
const engine = createCompletionEngine(catalog);
// Analyze a document
const source = "provider aws\nservice: elastic kubernetes";
const document = analyzeLanguageDocument(source);
// Get completions at cursor position
const offset = source.length; // cursor at end
const suggestions = engine.complete(document, offset);
// suggestions = [
// {
// label: "Amazon EKS",
// insertText: "eks",
// kind: "resource",
// searchTerms: ["elastic", "kubernetes", "eks"],
// replacement: { startOffset: 21, endOffset: 41 }
// },
// ...
// ]Monaco Integration Example
import * as monaco from "monaco-editor";
import { createCompletionEngine, analyzeLanguageDocument } from "@archlex/language-service";
import { createArchLex, awsProvider } from "@archlex/core";
// Create engine
const archlex = createArchLex({ providers: [awsProvider()] });
const catalog = archlex.getCatalog();
const engine = createCompletionEngine(catalog);
// Register Monaco provider
const disposable = monaco.languages.registerCompletionItemProvider("archlex", {
triggerCharacters: [":", ".", "[", "-"],
provideCompletionItems(model, position) {
const source = model.getValue();
const document = analyzeLanguageDocument(source);
const offset = model.getOffsetAt(position);
const completions = engine.complete(document, offset);
return {
suggestions: completions.map(c => ({
label: c.label,
insertText: c.insertText,
kind: monaco.languages.CompletionItemKind.Value,
range: {
startLineNumber: model.getPositionAt(c.replacement.startOffset).lineNumber,
startColumn: model.getPositionAt(c.replacement.startOffset).column,
endLineNumber: model.getPositionAt(c.replacement.endOffset).lineNumber,
endColumn: model.getPositionAt(c.replacement.endOffset).column,
}
}))
};
}
});
// Clean up when done
disposable.dispose();API Reference
analyzeLanguageDocument(source: string)
Parses ArchLex source and extracts structured metadata for completion.
Returns: LanguageDocument
source- Original source textproviderId- Current provider ("aws","gcp","k8s", ornull)scopePath- Current containment hierarchy (e.g.,["account", "region", "vpc"])symbols- Array of declared resource symbols with names, kinds, and positionsdirectives- Parsed directives (provider, direction, validation)
createCompletionEngine(catalog: CatalogMetadata)
Creates a completion engine backed by catalog metadata.
Parameters:
catalog- Catalog metadata fromarchlex.getCatalog()
Returns: CompletionEngine
CompletionEngine.complete(document: LanguageDocument, offset: number, options?: CompletionOptions)
Generates context-aware completions at the given offset.
Parameters:
document- Analyzed language documentoffset- Cursor position (0-based byte offset)options- Optional settingstrigger?: "manual" | "automatic"- How completion was invoked
Returns: LanguageCompletion[]
LanguageCompletion
interface LanguageCompletion {
label: string; // Display label (e.g., "Amazon EKS")
insertText: string; // Text to insert (e.g., "eks")
kind: CompletionKind; // "directive" | "resource" | "relationship" | "scope"
searchTerms: string[]; // Terms for fuzzy matching
replacement: {
startOffset: number; // Start of range to replace
endOffset: number; // End of range to replace
};
}getCursorContext(document: LanguageDocument, offset: number)
Determines the syntactic position at the cursor for context-aware completions.
Returns: CursorContext
position- Grammar position ("directive-name","resource-kind","relationship-type", etc.)providerId- Current providerscopePath- Current scope hierarchypartialText- Text being typed at cursor
createCatalogIndex(catalog: CatalogMetadata)
Creates a fast lookup index for catalog resources and relationships.
Returns: CatalogIndex
resolveResource(provider, kindOrAlias)- O(1) resource lookupsearchResources(provider, query)- Fuzzy search by human nameslistResources(provider)- All resources for a providergetRelationships(provider, sourceKind, targetKind)- Valid relationships
Features
Catalog-Driven Completions
All service names, relationships, and containment rules come from provider catalogs:
- 194 AWS services with relationships and containment
- 185 GCP services with relationships and containment
- 62 Kubernetes resources with relationships and containment
Human-Readable Search
Completions include searchable terms from service descriptions:
- Typing "elastic kubernetes" suggests
eks(Amazon EKS) - Typing "relational" suggests
rds(Amazon RDS) andaurora(Amazon Aurora) - Typing "forward" suggests
forwardsrelationship
Context-Aware Filtering
The engine filters suggestions based on:
- Current provider - Only shows AWS services when
provider awsis set - Scope hierarchy - Only shows resources valid in current containment (e.g.,
ecsinsidecluster) - Symbol visibility - Suggests declared resource identifiers for relationships
- Grammar position - Different suggestions after
:,[, or in directive positions
Semantic Ranking
Results are ranked by:
- Exact prefix match -
lam→lambdaranks higher - Search term relevance - Fuzzy match quality against human names
- Relationship compatibility - Valid relationships for source/target resources
Canonical Insertion
Completions always insert canonical syntax:
- Service kinds use lowercase kebab-case:
eks,cloud-run,statefulset - Relationships use lowercase:
connects,writes,publishes - Directives preserve required format:
provider aws,direction LR
Grammar Context Detection
The engine understands cursor position in the grammar:
| Context | Example | Completions |
|---------|---------|-------------|
| Directive name | prov█ | provider, direction, validation |
| Directive value | provider █ | aws, cloudflare, gcp, k8s |
| Resource kind | service: █ | AWS, Cloudflare, GCP, and Kubernetes services |
| Resource name | █: lambda | Identifier suggestions |
| Relationship type | a -[█ | connects, writes, etc. |
| Relationship target | a -[writes]-> █ | Declared identifiers |
Performance
- Document caching - WeakMap-based cache for repeated completions
- Incremental updates - Only re-parses when document version changes
- Fast lookups - O(1) catalog access, O(log n) search term matching
- Browser tested - Meets <50ms p95 latency on 100+ declaration documents
Architecture
- DOM-Neutral: No browser or editor dependencies - works in Node.js, browsers, and workers
- Immutable: All document and completion objects are readonly
- Editor-agnostic: Works with Monaco, VSCode, CodeMirror, or any editor supporting offsets
TypeScript Support
Fully typed with exported interfaces:
LanguageDocumentCompletionEngineLanguageCompletionCompletionKindCursorContextCatalogIndex
License
MIT
Resource prefix replacement
When the cursor is immediately after a resource identifier, its typed prefix
remains part of the completion query, including at the end of the document or
just before a newline. For provider aws\napi: lam, the lambda suggestion
replaces lam; it does not append lambda after that prefix. A cursor after
separating whitespace starts a new completion query instead.
