Skip to main content

01. Language surface

This note describes the user-facing surface of MEP-72. It is informative; the normative definitions live in MEP-72 §Specification §1-§5.

1. The import ts form

The Mochi grammar's FFI-import production from MEP-1 + MEP-52 phase 12:

ImportStmt := "import" Lang? StringLit "as" Ident ("auto")?
Lang := "go" | "python" | "typescript" | "ts" | "rust"

MEP-72 adds ts as the canonical short form. The full typescript keyword from MEP-52 phase 12 remains accepted (so old programs continue to parse; new programs are expected to use ts).

The <spec> admits five shapes:

import ts "zod" as z # bare name, npm-default
import ts "zod@^3.23.0" as z # semver-constrained, npm-default
import ts "npm:zod@^3.23.0" as z # explicit npm
import ts "jsr:@std/path@^1.0" as path # explicit JSR
import ts "lodash@git+https://github.com/lodash/lodash#main" as _
import ts "my-local@path+../local-pkg" as mine

The <alias> introduces a Mochi namespace; z.string() calls z.string() on the resolved JS import.

Why bare names default to npm

npm hosts 3.5M+ packages (April 2026). JSR hosts 12K+. A bare-name default to npm covers ~99.7% of the realistic dep graph; the user opts into JSR via the jsr: prefix on a per-import basis or via the [ts] default-registry = "jsr" global flag.

Why npm: and jsr: prefixes

These match the Deno toolchain's existing specifier syntax (import foo from "npm:foo", import foo from "jsr:@scope/foo"). The Deno surface is the established convention; MEP-72 inherits it directly.

2. The [ts-dependencies] table

Modelled on npm's package.json dependencies and Deno's imports import-map:

[ts-dependencies]
zod = "^3.23.0"
"@hono/zod-validator" = "^0.4"
"jsr:@std/path" = "^1.0"
typescript = { version = "^5.6", dev = true }
react = { version = "^18", peer = true }
"my-local-pkg" = { path = "../my-pkg" }
"my-git-fork" = { git = "https://github.com/example/my-fork", rev = "abc123" }

The simple-string and table-of-version forms mirror the existing npm grammar exactly so users can copy from package.json without translation. The dev = true flag marks the dep as a development-only dep (the bridge skips it from the published library's dependencies field, mirroring npm's devDependencies). The peer = true flag marks the dep as a peer the host package must provide (mirroring npm's peerDependencies).

3. The [ts] table

Mochi-specific build-time knobs:

[ts]
runtime = "node22" # node22 | deno2 | bun1.1 | browser | edge
module = "esm" # esm (default) | cjs (legacy)
target = "es2024"
default-registry = "npm" # npm (default) | jsr
import-map = "imports.json" # filename for Deno + browser import maps
monomorphise = [
{ item = "zod.ZodObject", T = "MyShape" },
]

4. The [ts.publish] table

Library publish knobs:

[ts.publish]
shape = "library" # library (default) | binary
exports = { "." = { "types" = "./dist/index.d.ts", "import" = "./dist/index.js" } }
sideEffects = false
files = ["dist/**", "src/**", "README.md", "LICENSE"]
license = "Apache-2.0 OR MIT"
publish-to = ["npm", "jsr"]

The publish-to field is a subset of ["npm", "jsr"]. mochi pkg publish honours this when no --to=... flag is passed; the CLI flag overrides.

5. The [ts.capabilities] table

[ts.capabilities]
net = true # node:net, node:http, undici, fetch
fs = false # node:fs
proc = false # node:child_process
worker = false # node:worker_threads, Worker, SharedWorker
wasm = false # WebAssembly.instantiate
eval = false # eval(), new Function()

Capability declarations are monotonic: if the manifest declares fs = false, a transitive dep that imports node:fs is a lock-time error.

6. The [ts.private] table

Opt-out for packages that have no Sigstore attestation:

[ts.private]
packages = ["@corp/internal-*"]
sigstore-skip = ["@corp/internal-*"]

7. CLI surface

CommandPurpose
mochi pkg add ts <pkg>[@<semver>]Adds entry to [ts-dependencies]; runs mochi pkg lock.
mochi pkg add ts npm:<pkg>[@<v>]Explicit npm registry.
mochi pkg add ts jsr:@scope/<pkg>[@<v>]Explicit JSR registry.
mochi pkg lockWalks [ts-dependencies], fetches tarballs, ingests .d.ts, writes [[npm-package]] + [[jsr-package]].
mochi pkg lock --checkVerifies all locked hashes.
mochi pkg lock --sigstore-requiredHardens lock to refuse un-attested versions.
mochi pkg publish --to=npmBuilds TargetNpmLibrary; dry-run locally, real publish via CI workflow.
mochi pkg publish --to=jsrBuilds TargetJsrLibrary; parallel to npm.
mochi pkg publish --to=bothBoth registries from the same workflow run.
mochi pkg sync tsRegenerates Mochi shim files from the locked ApiSurface (without changing the lockfile).

8. Per-import alias semantics

The <alias> introduces a Mochi namespace. Item names preserve the original TS casing (no rename), because the runtime resolution targets the original identifier; renaming would break the link. The Mochi side accepts <alias>.<item> as the syntactic form even though Mochi convention is snake_case (this is the only place in Mochi where camelCase / PascalCase identifiers are admitted, and it is exactly the existing MEP-52 phase 12 behaviour for import typescript).

The auto modifier opts into top-level binding (every exported item becomes a top-level Mochi binding rather than namespaced under the alias). This matches MEP-52 phase 12's existing behaviour for import typescript ... auto.

9. Cross-references