Skip to content

Add package imports (RFC 0018) - #105

Open
bjacobso wants to merge 2 commits into
bjacobso/imported-ir-typesfrom
bjacobso/packages
Open

bjacobso wants to merge 2 commits into
bjacobso/imported-ir-typesfrom
bjacobso/packages

Conversation

@bjacobso

@bjacobso bjacobso commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #103.

Summary

Forma modules can now import from packages, and RFC 0018 specifies how.

(import "@acme/flows" [flow])       ; the "." export
(import "@acme/flows/ir" [FlowIR])  ; the "./ir" export

Decisions (RFC 0018)

  • The host's package manager handles distribution. Forma packages are npm packages. npm, pnpm, or yarn install them and lock their versions; Forma defines no registry, lockfile, or version solver. This replaces RFC 0002's forma.toml sketch.
  • The manifest is the forma field of package.json. exports maps "." / "./sub" to files, and only those files can be imported from outside. An optional prelude gives the package's own modules automatic imports.
  • Dependencies must be declared. A module may import only packages its own package declares (dependencies, peerDependencies, optionalDependencies, plus devDependencies for the root).
  • Identities don't depend on install location. Package modules are pkg:<name>/<path>, without the version. Binding identities, generated names, and contractIds match across machines and stay the same across releases that don't change a definition. The root package's modules keep their file paths, so diagnostics stay clickable.
  • A graph contains one version of each package. That keeps nominal types unambiguous.
  • Errors at the specifier: module/undeclared-dependency, module/package-not-found, module/package-private (lists the exports), module/package-escape, module/specifier. From the host table: module/package-version-conflict, module/package-table.

Implementation

  • Compiler (@formalang/ts/modules): PackageTable, withPackages, pkg: IDs, and package preludes through the existing project-prelude mechanism. resolveModuleGraph accepts packages. The resolver contract is unchanged: a bare specifier becomes ./<export> from importer pkg:<name>/, so sourceModuleResolver works as is.
  • Node (@formalang/ts/node): readNodePackages(entry) finds the nearest package.json, follows Forma dependencies through node_modules, and rejects version conflicts. nodeModuleResolver reads package files, and readModuleGraph uses both.
  • Host: configureSession({ packages }) and its JSON ABI schema. The forma file CLI configures the session from the Node table.
  • Fix: session loading bootstrapped a file module's own forms as session descriptors, so a module form whose :ir type was imported failed at load with unknown IR type. That affected Let a form produce an :ir type another module exports #103's feature in sessions and the CLI, not just packages. File modules are now left to the module graph.

The language server doesn't resolve packages yet (RFC stage 2). Publishing the repository preludes as packages, so that Open Ontology can stop vendoring them, is stage 3.

Docs: RFC 0018, docs/modules.md "Packages", RFC 0002 cross-links, docs/agents.md, sidebar. Changeset included.

Tests

  • packages/ts/test/packages.test.ts:
    • in-memory resolution and identities, including contractId through a package
    • each specifier error at its span
    • a package can't use its consumer's dependencies
    • package preludes, and table validation
    • Node tables built from temp node_modules
    • the same contractId from two install locations
    • a version conflict
  • packages/host/test/packages.test.ts: a JSON ABI session with a package table, including an undeclared dependency, and forma file typecheck / interface against installed packages.
  • pnpm check passes. No goldens changed.

🤖 Generated with Claude Code


Open workspace in Conductor

bjacobso and others added 2 commits November 1, 2026 21:37
(import "@acme/flows/ir" [FlowIR]) resolves through the forma.exports
field of an installed package's package.json. A module may import only the
packages its own package declares, relative imports may not leave a
package, and package modules get location-independent pkg:<name>/<path>
identities, so binding identities and contractIds match across machines.

The compiler takes a host package table (ModuleCoreOptions.packages,
configureSession({ packages })); @formalang/ts/node builds one from
package.json files and node_modules and rejects two versions of one
package; the forma file commands use it. Packages may declare a prelude
for their own modules. Session loading no longer bootstraps a file
module's own forms as session descriptors, so a module form can use an
imported :ir type in sessions and the CLI.

RFC 0018 replaces RFC 0002's forma.toml and lockfile sketch: npm, pnpm,
or yarn install and lock packages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant