Skip to content

Dependencies and files

Joshua Reynolds edited this page Oct 7, 2026 · 1 revision

Dependencies and files

Declarations must come from an available source: the current scope, an explicit import/include, or a declared library. Built-ins need no import. Merely supplying another file somewhere does not make all its names ambient.

Import a cart

Use use to select names. Put these two files beside one another:

main.expec

use Cart from "./cart.expec"
concept StoreGame {
  depends on Cart
  public save
  capability save(cart: Cart) returns Nothing
}

cart.expec

type Cart {
  itemsCount: Number
}

The Cart type and its itemsCount field come from cart.expec. The import makes that declaration available; the dependency clause records StoreGame's explicit dependency on it. A missing or misspelled import is an error.

Import aliases

Select more than one declaration with commas; rename a selection using as.

main.expec

use Cart as Basket, Book from "./catalog.expec"
function choose(cart: Basket) returns Book

catalog.expec

type Cart { itemsCount: Number }
type Book { title: Text }

An alias refers to the original declaration. It does not create a second nominal record or re-export every import of the supplying file. Public capabilities can be selected through their owner; local declarations stay private.

Include declarations

include exposes a file's declarations for composition, retaining their original identity and source context.

main.expec

include "./shared.expec"
function open(settings: Settings) returns Nothing

shared.expec

type Settings { title: Text }

Relative locators resolve from the file that contains them. Included declarations keep their own dependencies: a caller cannot repair a missing dependency inside an included file by declaring an unrelated name.

Include cycles and conflicting visible declarations are errors. Reaching the same declaration through multiple paths does not make it a new declaration each time. A supplying file's own imported aliases do not become ambient names in its caller.

Separate examples

Keep the contract in main.expec and the longer examples in saving.examples.expec:

main.expec

concept StoreGame {
  public save
  capability save(snapshot: Text) returns Nothing
}
examples for StoreGame from "./saving.examples.expec"

saving.examples.expec

examples {
  scenario "save the current snapshot" {
    when save("current")
    then satisfies "The snapshot is stored."
  }
}

The attachment supplies the subject context to the separate examples. The examples retain their own source locations. There is no bindings keyword.

extend can also contribute members when sources are composed; see Concepts and construction.

Packages and phases

A package requirement names an alias supplied by the project configuration:

main.expec

concept StoreGame {
  requires package "tests" for test
  public start
  capability start() returns Nothing
}

This example needs the following package requirement in expec.json, along with the usual format, version and source entries:

{
  "formatVersion": 1,
  "version": "0.1.0",
  "build": { "entries": ["main.expec"] },
  "outputs": [],
  "packages": [
    { "alias": "tests", "name": "npm:vitest", "version": "5.0.2", "phases": ["test"] }
  ]
}

The available phases are build, runtime and test. A phase-qualified requirement must be supplied in that phase. An unqualified requires package "tests" checks availability without inventing a phase.

This declares a requirement; it does not install it. Explicit installation is a separate operation. Manifest libraries describe available specification libraries; manifest packages describe native package requirements. Importing a native library is not the same as importing arbitrary .expec declarations from it.

Related: Types · Examples and scenarios · Project configuration and commands

Clone this wiki locally