Local Module Imports

Split a program across .tjs files with ordinary import and export. Each function still checks its own arguments (the checks its parameter examples create), whichever file calls it.

Live examples on this site each run by themselves, with no other files around them. So this page shows the module first, which runs here, then a program that imports it, which is shown without running. After that comes the recipe for running both on your own machine.

The module: mymath.tjs

export function add(a: 2, b: 3): 5 {
  return a + b
}

export function multiply(a: 2, b: 3): 6 {
  return a * b
}

test 'add and multiply' {
  expect(add(10, 5)).toBe(15)
  expect(multiply(10, 5)).toBe(50)
}

Each signature is a worked example that TJS runs: the parameter examples are the inputs and the return example is the expected output. So add(a: 2, b: 3): 5 checks that add(2, 3) gives 5, and multiply(a: 2, b: 3): 6 checks that multiply(2, 3) gives 6. The test block adds two more cases. The module has no imports, so all of this runs when it is transpiled, here or anywhere else.

The program that imports it: main.tjs

// main.tjs
import { add, multiply } from './mymath.tjs'

function calculate(x: 3, y: 4): 14 {
  // (x + y) * 2
  return multiply(add(x, y), 2)
}

console.log('calculate(3, 4) =', calculate(3, 4))
console.log('calculate(10, 5) =', calculate(10, 5))

// A bad call into the other module is refused there, by add itself
console.log(add(1.5, 2).message)

Run beside mymath.tjs (recipe below), it prints:

calculate(3, 4) = 14
calculate(10, 5) = 30
Expected integer for 'mymath.tjs:1:add.a', got number

What to notice

A TJS file is an ES module. export and import mean what they mean in JavaScript, and a function's checks go with it across the file boundary. add and multiply validate their own inputs wherever they are called from.

That is what the last line of main.tjs shows. add returns an error object (a MonadicError) instead of throwing, so .message can be read straight off the result. main.tjs wrote no validation code; the check belongs to add, so every caller, in any file, gets it.

The message says where the check lives: mymath.tjs:1:add.a is parameter a of add, declared on line 1 of mymath.tjs. "Expected integer, got number" is because a: 2 is an integer example, and TJS treats integer as a narrower type than number: 1.5 is a number but not an integer. Use 0.0 as the example to accept any number (see Hello TJS). Error Handling shows how to test for an error with isMonadicError.

calculate's signature example, (x: 3, y: 4): 14, does not run when main.tjs is checked. TJS runs a file's signature examples and test blocks by executing that file on its own, without loading its imports, so anything that calls an import comes out inconclusive: not a pass, not a failure. tjs check main.tjs accepts the file, and tjs test main.tjs reports:

? Signature: calculate signature example — inconclusive: Module could not be executed for testing: multiply is not defined

That is why the real tests belong in mymath.tjs, where the functions are defined. To test calculate itself, see the last gotcha.

Running it on your machine

This recipe uses Bun. Install tjs-lang in your project (bun add -d tjs-lang), put the two files side by side, and tell Bun to load .tjs files through the TJS plugin with a bunfig.toml in the same directory:

my-project/
  bunfig.toml
  main.tjs
  mymath.tjs
# bunfig.toml
preload = ["tjs-lang/bun-plugin"]

[test]
preload = ["tjs-lang/bun-plugin"]

Then either of these runs main.tjs and prints the three lines shown above:

bun main.tjs
bunx tjs run main.tjs

bunx tjs runs the tjs command from your local tjs-lang. That command runs on Bun, so it reads the same bunfig.toml: the first preload covers bun and tjs run, and the [test] one covers bun test.

Without the preload, Bun does not read .tjs files as TJS, and both commands stop with a misleading message: Export named 'add' not found in module '…/mymath.tjs'. The exports are there; Bun cannot see them without the plugin.

For the full project setup (package.json, tjs emit, bundlers), see Running TJS.

Gotchas