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.shopversion: "1.4.0"
dependencies { acme.shared: "../shared" acme.payments: "../packages/payments"}
use * from arch.modulesuse * from arch.kindsuse Database, Cache from acme.sharedThat’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’sstdlib/directory claimingarch.<anything>emitsRESERVED_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:
- Resolves the path against the manifest’s directory.
- Loads the dependency package recursively (its own dependencies load too).
- Verifies the loaded package’s
name:matches the depender’s declared name. Mismatch emitsDEP_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 typeuse database from arch.modules
// Severaluse service, frontend from arch.modules
// Wildcard — every exported typeuse * from arch.modules
// Renameuse database as managed_db from arch.modules
// Re-export so consumers of THIS package see it tooexport use payments_provider from acme.payments| Form | Effect |
|---|---|
use X from p | Imports X directly. X must be marked export in p — otherwise USE_TYPE_NOT_EXPORTED. |
use * from p | Imports every type marked export in p. |
use X as Y from p | Imports X under the local name Y. |
export use X from p | Re-exports X so wildcard importers of this package also receive X. |
Visibility scope
- A
usedeclared inpackage.archspaceis package-scoped: every.archfile in this package can reference the imported names. - A
usedeclared inside a.archfile is file-scoped: only that file can reference them. References from a sibling file emitKIND_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
| Code | When |
|---|---|
MANIFEST_PARSE_ERROR | Malformed manifest syntax |
RESERVED_PACKAGE_NAMESPACE | A non-stdlib package claims the arch.* prefix |
DEP_LOAD_FAILED | A declared dependency path doesn’t exist or fails to parse |
DEP_NAME_MISMATCH | The loaded package’s name: differs from the depender’s expectation |
DEP_CYCLE | A cycle in the package dependency graph |
STDLIB_NOT_FOUND | An arch.* import couldn’t be resolved against the configured stdlib |
USE_PACKAGE_NOT_FOUND | use ... from <pkg> references a package neither in dependencies nor arch.* |
USE_TYPE_NOT_FOUND | The named type doesn’t exist in the source package |
USE_TYPE_NOT_EXPORTED | The type exists but isn’t marked export |
USE_NAME_COLLISION | The same local name imported from two different source packages |
WIDGETS_FILE_NOT_FOUND | The widgets: path doesn’t resolve to an existing file |
Worked examples
Minimal scratchpad:
name: scratchA 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.shopversion: "0.4.0"
use * from arch.modulesuse * from arch.kindsThe most common shape. Pulls in the bundled module kinds (service, database, …) and interaction-style interface kinds (command, query, event, …).
Multi-package monorepo:
name: acme.appversion: "1.0.0"widgets: "./widgets.js"
dependencies { acme.shared: "../shared" acme.payments: "../packages/payments"}
use * from arch.modulesuse Database, Cache from acme.sharedexport use payments_provider from acme.paymentsDeclares deps, project-wide imports, plus a re-export so anything depending on acme.app also picks up payments_provider.
Library package:
name: acme.sharedversion: "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.archA 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.archspacemanifest at its root. - Fields:
name:(required),version:,widgets:,dependencies { },use … from …. arch.*is reserved for stdlib and auto-resolves; user dependencies need a path.usein the manifest is package-scoped;usein a.archfile 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.