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
Write the extension.
from './mymath.tjs'works;from './mymath'fails withCannot find module './mymath'. The import is resolved by Bun, which does not guess.tjs.Bare names worked only in the old playground. It could import a saved module by name (
from 'mymath'). In a project, import by relative path as above.To test code that calls an import, use
bun test. Putcalculatein a module that only exports it, so importing it prints nothing:// calc.tjs import { add, multiply } from './mymath.tjs' export function calculate(x: 3, y: 4): 14 { return multiply(add(x, y), 2) }Then test it from TypeScript. The
[test]preload above is what letsbun testimport a.tjsfile:// calc.test.ts import { test, expect } from 'bun:test' import { calculate } from './calc.tjs' test('calculate', () => { expect(calculate(3, 4)).toBe(14) expect(calculate(10, 5)).toBe(30) })