Skip to content

12. Packages

A package is a directory containing a package.archspace manifest and any number of .arch files under it. The manifest names the package, declares its dependencies on other packages, points to a widgets script, and selects which kinds are imported into scope.

name: acme.shop
version: "1.4.0"
dependencies {
acme.shared: "../shared"
acme.payments: "../packages/payments"
}
use * from arch.modules
use * from arch.kinds
use Database, Cache from acme.shared

That’s a full-shaped manifest. This chapter covers every field, every diagnostic, and the resolution rules.

Anonymous packages

A directory without a manifest is an anonymous package: the loader walks every .arch file under it and resolves them together with no name, no dependencies, no exports. Anonymous packages are for tiny scratchpads and backwards compatibility. Real projects always have a manifest.

Fields

name: (required, exactly once)

The package identifier. Dotted form is conventional:

name: acme.shop
  • At least one segment.
  • Lowercase by convention; not enforced.
  • The arch.* prefix is reserved for the bundled stdlib. A user package outside the toolchain’s stdlib/ directory claiming arch.<anything> emits RESERVED_PACKAGE_NAMESPACE.

Duplicate name: lines are an error; the first occurrence wins so tooling has something stable to work with.

version: (optional, exactly once)

version: "1.4.0"

Free-form string. Currently informational only — the loader doesn’t parse, compare, or constrain versions. Reserve the field for forward compatibility; future tooling will use semver-style ranges.

widgets: (optional, exactly once)

Path (relative to the manifest dir) to a JS/TS module that registers custom-element widgets via customElements.define():

widgets: "./widgets.js"

The viewer dynamic-imports this script at boot and re-imports it on file changes (after a full page reload — customElements.define is one-shot per tag name).

The loader verifies the file exists at load time. A typo emits WIDGETS_FILE_NOT_FOUND immediately. The widget pipeline is the subject of Chapter 20.

dependencies { ... } (optional, at most one block)

Map from dependency package name to filesystem path:

dependencies {
acme.shared: "../shared"
acme.payments: "../../packages/payments"
}

For each entry, the loader:

  1. Resolves the path against the manifest’s directory.
  2. Loads the dependency package recursively (its own dependencies load too).
  3. Verifies the loaded package’s name: matches the depender’s declared name. Mismatch emits DEP_NAME_MISMATCH.

Diamond dependencies (A→B, A→C, both →D) load D once and share the same instance — the loader caches by absolute path.

Cycles (A→B, B→A) emit DEP_CYCLE on every package along the cycle.

arch.* packages don’t need an entry. The loader auto-resolves them against the toolchain’s bundled stdlib (configurable via ARCHLANG_STDLIB).

use … from <pkg> (zero or more)

Imports types from a dependency or stdlib package:

// Import a single type
use database from arch.modules
// Several
use service, frontend from arch.modules
// Wildcard — every exported type
use * from arch.modules
// Rename
use database as managed_db from arch.modules
// Re-export so consumers of THIS package see it too
export use payments_provider from acme.payments
FormEffect
use X from pImports X directly. X must be marked export in p — otherwise USE_TYPE_NOT_EXPORTED.
use * from pImports every type marked export in p.
use X as Y from pImports X under the local name Y.
export use X from pRe-exports X so wildcard importers of this package also receive X.

Visibility scope

  • A use declared in package.archspace is package-scoped: every .arch file in this package can reference the imported names.
  • A use declared inside a .arch file is file-scoped: only that file can reference them. References from a sibling file emit KIND_NOT_VISIBLE_IN_FILE.

The same name imported from the same source package across multiple files is fine — scopes accumulate. The same name from different source packages produces USE_NAME_COLLISION; rename one with as.

Anonymous packages skip the export check: every type is reachable. Once a package gains a manifest, types must be marked export to surface to dependents.

Diagnostic codes

CodeWhen
MANIFEST_PARSE_ERRORMalformed manifest syntax
RESERVED_PACKAGE_NAMESPACEA non-stdlib package claims the arch.* prefix
DEP_LOAD_FAILEDA declared dependency path doesn’t exist or fails to parse
DEP_NAME_MISMATCHThe loaded package’s name: differs from the depender’s expectation
DEP_CYCLEA cycle in the package dependency graph
STDLIB_NOT_FOUNDAn arch.* import couldn’t be resolved against the configured stdlib
USE_PACKAGE_NOT_FOUNDuse ... from <pkg> references a package neither in dependencies nor arch.*
USE_TYPE_NOT_FOUNDThe named type doesn’t exist in the source package
USE_TYPE_NOT_EXPORTEDThe type exists but isn’t marked export
USE_NAME_COLLISIONThe same local name imported from two different source packages
WIDGETS_FILE_NOT_FOUNDThe widgets: path doesn’t resolve to an existing file

Worked examples

Minimal scratchpad:

name: scratch

A scratch project that doesn’t depend on anything. Every .arch file in the directory is loaded and gets to import from each other; no arch.* types are visible because use isn’t declared.

Standard project using stdlib:

name: acme.shop
version: "0.4.0"
use * from arch.modules
use * from arch.kinds

The most common shape. Pulls in the bundled module kinds (service, database, …) and interaction-style interface kinds (command, query, event, …).

Multi-package monorepo:

name: acme.app
version: "1.0.0"
widgets: "./widgets.js"
dependencies {
acme.shared: "../shared"
acme.payments: "../packages/payments"
}
use * from arch.modules
use Database, Cache from acme.shared
export use payments_provider from acme.payments

Declares deps, project-wide imports, plus a re-export so anything depending on acme.app also picks up payments_provider.

Library package:

name: acme.shared
version: "2.1.0"

The library defines types in its .arch files and marks the public ones with export. Consumers use them by name; non-exported types remain internal.

File layout

my-project/
├── package.archspace # this file
├── widgets.js # custom-element registrations (optional)
├── orders.arch # types / modules / processes / views
├── kinds.arch # project-local types
└── packages/
└── shared/
├── package.archspace # nested package — its OWN unit
└── lib.arch

A nested package.archspace is a hard boundary: the parent’s file walk stops at the nested manifest’s directory, and the nested package resolves as its own unit when explicitly referenced via dependencies or auto-loaded as arch.*.

Summary

  • Every project has a package.archspace manifest at its root.
  • Fields: name: (required), version:, widgets:, dependencies { }, use … from ….
  • arch.* is reserved for stdlib and auto-resolves; user dependencies need a path.
  • use in the manifest is package-scoped; use in a .arch file is file-scoped.
  • Diamond deps share one instance; cycles emit DEP_CYCLE.

What’s next

Chapter 13: Stable IDs → — identity that survives renames, and where IDs do and don’t apply.