txiki.js
the runtime your backend runs on
The backend runs on txiki.js, a small JavaScript runtime: the QuickJS engine, libuv for I/O, with SQLite and WebAssembly built in. tinyjs ships txiki.js 26.6.0. Everything txiki.js offers is available to your backend — this page is a map of it, with links to txiki.js's own reference for the details.
It isn't Node.js: there's no require, no process, no Buffer and no node:* modules. Use the tjs global, the tjs:* modules and standard Web APIs instead. npm packages work in a TypeScript backend (it's bundled with esbuild) as long as they don't need Node's built-ins.
The page has none of this — it runs in the system webview, with no tjs. Expose what it needs through api functions.
the tjs global
| area | calls | reference |
|---|---|---|
| files | readFile, writeFile, open, stat, lstat, readDir, makeDir, makeTempDir, makeTempFile, remove, rename, copyFile, symlink, readLink, realPath, chmod, chown, utime, statFs, watch | filesystem |
| processes | spawn, exec, kill, exit, pid, ppid, addSignalListener | process |
| network | connect, listen, lookup (DNS) | networking |
| HTTP server | serve | HTTP server |
| system | args, env, cwd, chdir, homeDir, tmpDir, hostName, exePath, system, version | system |
| console & I/O | stdin, stdout, stderr, createConsole | utilities |
| engine | engine — versions, GC, features | engine |
| modules | setImportMap | modules |
const text = new TextDecoder().decode(await tjs.readFile(path));
await tjs.writeFile(out, new TextEncoder().encode(text.toUpperCase()));
const p = tjs.spawn(['git', 'status', '--short'], { stdout: 'pipe' });
let output = '';
for await (const chunk of p.stdout) output += new TextDecoder().decode(chunk);
await p.wait();On Windows, use app.spawnHidden() rather than tjs.spawn() for console programs, so each one doesn't flash a terminal window.
tjs: modules
| module | for | reference |
|---|---|---|
tjs:sqlite | a SQLite database — see sqlite | tjs:sqlite |
tjs:path | joining and splitting paths | tjs:path |
tjs:hashing | MD5, SHA-1, SHA-256 and other digests | tjs:hashing |
tjs:uuid | UUIDs | tjs:uuid |
tjs:ffi | calling C libraries directly | tjs:ffi |
tjs:wasi | running WASI WebAssembly modules | tjs:wasi |
tjs:posix-socket | raw sockets | tjs:posix-socket |
tjs:ipaddr | parsing IP addresses | tjs:ipaddr |
tjs:readline, tjs:getopts | terminal input and command-line options, for command-line tools | readline, getopts |
tjs:assert, tjs:utils | assertions; format and inspect | assert, utils |
import path from 'tjs:path';
import { createHash } from 'tjs:hashing';
const file = path.join((await app.paths()).data, 'notes.json');
const digest = createHash('sha256').update(text).digest();Web APIs
The backend also has the standard Web APIs txiki.js implements, including fetch, WebSocket, Worker, crypto (with crypto.subtle and randomUUID), TextEncoder/TextDecoder, URL, Blob, FormData, AbortController, CompressionStream, structuredClone, performance and localStorage. See txiki.js's Web Platform APIs for the full list.
For an app's settings, prefer app.store to localStorage: it's the same store the page sees, saved in the app's data folder.
types
Every project gets txiki.js's own type definitions as types/tjs.d.ts (backend/tjs.d.ts in the Vite templates), so editors complete and check tjs.* calls and tjs:* imports — no npm needed. They match the txiki.js version tinyjs ships, and tinyjs update-project brings them up to date after you update tinyjs.
They're for the backend only: the Vite templates' src/ is the page, which has no tjs, so it doesn't get them.
versions
txiki.js's website documents its latest release, which can be newer than the version tinyjs ships. If something there doesn't exist in your backend, check tjs.version — or tjs.engine.versions for QuickJS, libuv and SQLite. A tinyjs release names the txiki.js version it moves to in the changelog.
Sharp edges worth knowing — async file calls next to sync SQLite, stdout: 'ignore' vs 'null' — are on gotchas.