-
Notifications
You must be signed in to change notification settings - Fork 0
Composition and Imports
Composition lets a project split vocabulary across files while still generating and searching one connected graph.
Split a lexicon when a vocabulary has different owners or update cadences:
-
shared-commerce.lexiconfor reusable product, UI, and data types. -
storefront-api.lexiconfor API terms owned by backend engineers. -
product-ui.lexiconfor UI terms owned by app engineers and designers. -
analytics.lexiconfor event names and funnel states. -
support.lexiconfor operational language and support workflows.
Keep one root lexicon that imports the pieces used by a generated target or editor workspace.
@ ./shared-commerce.lexicon
commerce:
api:
order:
A document-level import composes the imported document with the current document. Use this for shared roots that should participate in the same graph.
commerce:
api:
storefront:
@ ./storefront-api.lexicon
ui:
product:
@ ./product-ui.lexicon
A node-level import grafts the imported root under the node where the import appears. This lets a file own a local subtree while the root lexicon controls where it lands.
lexicons/
commerce.lexicon
shared-commerce.lexicon
api/
storefront.lexicon
ui/
product.lexicon
analytics/
events.lexicon
lexicon-lsp.json
commerce.lexicon:
@ ./shared-commerce.lexicon
commerce:
api:
storefront:
@ ./api/storefront.lexicon
ui:
product:
@ ./ui/product.lexicon
analytics:
@ ./analytics/events.lexicon
lexicon-lsp.json:
{
"lexicon": "lexicons/commerce.lexicon"
}The editor and generator now see the same composed graph.
let source = URL(fileURLWithPath: "lexicons/commerce.lexicon")
let document = try TaskPaper(Data(contentsOf: source)).decodeDocument()
let resolver = FileLexiconImportResolver(baseURL: source.deletingLastPathComponent())
let plan = try document.composed(resolving: resolver)
guard plan.conflicts.isEmpty else {
throw ValidationError(plan.conflicts.map(\.description).joined(separator: "\n"))
}
let composed = plan.document
let lexicon = try await Lexicon.from(composed, root: "commerce")Composition reports conflicts instead of silently choosing a winner. Treat conflicts as design feedback:
- Two files declare the same concrete node with incompatible metadata.
- A graft point would cause a path collision.
- An imported branch carries references that cannot be resolved after composition.
Resolve conflicts by making ownership explicit. Usually that means moving the shared part into a shared import, renaming a local dialect, or replacing duplicate structure with a synonym.
The CLI can export a subtree as a self-contained fragment:
swift run lexicon excerpt commerce.lexicon commerce.ui.product.cardExternal references are reported as diagnostics. Lexicon adds imports where it can infer them, so exported branches can become new shared files without losing their dependencies.
The file import resolver resolves local imports relative to the base URL of the source lexicon. Remote imports are intentionally restricted to HTTP(S). Do not depend on arbitrary local absolute paths in project lexicons; keep imports relative to the root lexicon directory.