A pure-TypeScript jq JSON processor that runs as a regular sandboxed Mithic process. It is a from-scratch reimplementation of the jq filter language — lexer, parser, tree-walking interpreter, and builtins — with no native binary and no WebAssembly. As a guest it reads JSON from stdin, applies a filter program, and writes results to stdout; as a library it exposes the engine for embedding jq directly in JavaScript.
This package replaces the previous WASM-based jq command.
The language engine is a four-stage pipeline (@mithic/jq/engine):
lex(lexer.ts) — tokenizes a program into a flatToken[]. Handles numbers, strings with JSON escapes and\(…)interpolation,@formatstrings,$var, the.-prefixed forms (.foo,.[,..), keywords, and greedy multi-char operators (//,?//,|=,//=, comparisons, …).parse(parser.ts) — recursive-descent over the tokens, producing the tagged-unionNodeAST (ast.ts). Precedence (low→high): pipe|, comma,,//alternative, assignment operators,or,and, comparison, additive, multiplicative, unary-, then postfix suffixes (.foo,[…],?). Assignments (=,|=,+=, …) are desugared into interpreter-handled calls.evalNode(interp.ts) — a generator-based evaluator: every filter is a1 → manymapping, so streaming and backtracking (,,.[],//,reduce, filter-valued args) fall out ofyield*over nested generators. The lexical scope chain (Env) is immutable, holding$variables, userdeffunctions, and filter-valued (closure) parameters.error,label/break, andlimitare implemented via thrown control signals.builtins.ts— the builtin functions, split into simple builtins (data driven, keyed byname/arity, args materialized as a cartesian product) and higher-order builtins (map,select,reduce-likes,recurse,limit, regexsub/gsub, path builtins, …) that re-enter the evaluator with the raw arg nodes.values.tsholds jq's type names, total order, deep equality, and the canonical serializer used bytojson/@json/-S.
Around the engine:
cli.ts—parseJqArgs(argv →JqOptions),parseInputs/parseJsonStream(the whitespace-separated JSON input stream), andformatOutput(renders one output value per the output flags).jq.ts— the guest entry module (built todist/jq.js): reads stdin, runs the filter over each input, and writes formatted results.resolver.ts—resolveJq/createJqResolver, aresolveCommand(name, cwd, env)factory that maps the name"jq"to the built guest module URL fornew Kernel({ resolveCommand }).
import { Kernel } from '@mithic/kernel';
import { resolveJq } from '@mithic/jq';
const kernel = new Kernel({ runtime, vfs, resolveCommand: resolveJq });
// A guest can now spawn `jq` by name; compose with other resolvers as needed:
const resolveCommand = (name, cwd, env) =>
resolveJq(name, cwd, env) ?? createCoreutilsResolver()(name, cwd, env);import { compile, run } from '@mithic/jq';
run('.users | map(.name)', { users: [{ name: 'a' }, { name: 'b' }] });
// → [['a', 'b']]
// Compile once, apply to many inputs:
const f = compile('.value * 2');
[...f.run({ value: 21 })]; // → [42]run(program, input, options) returns all outputs as an array; compile
returns a CompiledFilter whose run(input, options) is a generator. Both
accept { env, args } — args are bound as $name (from --arg/--argjson)
and exposed via $ARGS.named; env backs the env builtin and $ENV.
jq [FLAGS] FILTER reads its JSON input from stdin (argv after the filter is
accepted but file operands are not read in the pipe).
| Flag | Meaning |
|---|---|
-r / --raw-output |
emit raw strings (no JSON quoting) |
-j / --join-output |
-r with no separator between outputs |
-c / --compact-output |
single-line JSON |
-n / --null-input |
run once with null input (don't read stdin) |
-s / --slurp |
read the whole input stream into one array |
-R / --raw-input |
each input line is a raw string (with -s, the whole text) |
-e / --exit-status |
exit non-zero when the last output is false/null/absent |
-a / --ascii-output |
escape non-ASCII as \uXXXX |
-S / --sort-keys |
sort object keys in output |
--tab |
indent with tabs |
--indent N |
indent with N spaces (default 2) |
--arg NAME VALUE |
bind $NAME to the string VALUE |
--argjson NAME JSON |
bind $NAME to a parsed JSON value |
Short flags cluster (e.g. -rc); -- ends flag parsing; unknown flags are
ignored. Exit codes: 0 success, 3 compile (syntax) error, 4 -e with no
output, 5 runtime error, plus any code requested by halt_error.
The engine covers the bulk of the jq language:
- Path & iteration: identity
., field.foo/."str", index.[e], slice.[a:b], iterate.[], recursive descent.., optional?, and pipes|. - Construction: array
[…]and object{…}literals, including the{$x}/{foo}/{(expr): …}/{"k": …}key forms and string interpolation"\(.x)". - Operators:
+ - * / %with jq's per-type semantics (array concat/subtract, object merge, string repeat/split), comparisons,and/or/not, the//alternative operator, and unary minus. - Assignment / update:
=,|=,+=,-=,*=,/=,%=,//=over path expressions, plussetpath/getpath/delpaths/paths/del. - Control flow:
if/elif/else/end,try/catch,reduce … as $p (init; upd),foreach … as $p (init; upd; extract),label $out | … break $out, and destructuring bindingEXP as PATTERN | …(array / object /?//alternative patterns). - Definitions: user functions
def f(args): …;with$valueand filter-valued (closure) parameters and recursion. - Builtins (selection):
length,keys/keys_unsorted,has/in,contains/inside,type,add,map/map_values/select,to_entries/from_entries/with_entries,sort/sort_by/group_by/unique/unique_by,min/max/min_by/max_by,range,first/last/nth/limit,recurse/walk/until/while/repeat,flatten/reverse/join/split,paths/leaf_paths/getpath/setpath/del, type predicates (objects,arrays,numbers,strings,nulls,iterables,scalars,values), string functions (ascii_upcase/ascii_downcase,ltrimstr/rtrimstr,startswith/endswith,explode/implode,tostring/tonumber/tojson/fromjson), math (floor/ceil/round/sqrt/pow/log/exp/…), regex (test,match,capture,scan-stylesplits,sub/gsub), andenv/now/error/halt/halt_error. @formatstrings:@text,@json,@base64/@base64d,@uri,@csv,@tsv,@html,@sh— usable as bare filters, as@fmt "…", and inside string interpolation.
Regex uses JavaScript's
RegExprather than Oniguruma; common flags (g,i,s,m,p) map closely, while Oniguruma-only features (e.g. the extendedxflag) are best-effort or ignored. Modules (import/include), SQL-style builtins, and$__loc__line tracking beyond a fixed stub are not implemented.
npm run build && npm test # build first — tests import from dist/