MCPcopy Index your code
hub / github.com/RecoLabs/gnata

github.com/RecoLabs/gnata @v0.2.2

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.2.2 ↗ · + Follow
675 symbols 2,039 edges 58 files 333 documented · 49%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README
<img src="https://github.com/RecoLabs/gnata/raw/v0.2.2/assets/gnata-light.png" alt="gnata" width="720" />

A full JSONata 2.x implementation in Go, built for production streaming workloads.

CI Go Reference Go version MIT License npm version

Quick Start · Streaming API · Metrics · Performance · Compatibility · WASM Playground · Contributing · Security


What is gnata?

JSONata is a lightweight query and transformation language for JSON data — think "jq meets XPath with lambda functions." gnata brings the full JSONata 2.x specification to Go, with a production-grade streaming tier designed for evaluating thousands of expressions against millions of events per day with zero contention.

Features

  • Full JSONata 2.x — path navigation, wildcards, descendants, predicates, sorting, grouping, lambdas, closures, higher-order functions, transforms, regex, and the complete 50+ function standard library.
  • Two-tier evaluation — simple expressions use a zero-copy fast path (GJSON); complex expressions fall back to a full AST evaluator.
  • Lock-free streamingStreamEvaluator batches multiple expressions per event with schema-keyed plan caching. After warm-up, the hot path uses only atomic loads — no mutexes, no RWLocks, no channels.
  • Zero allocations — simple field comparisons like user.email = "admin@co.com" evaluate with 0 heap allocations via GJSON zero-copy string views.
  • Bounded memory — schema plan cache uses a FIFO ring-buffer with configurable capacity (WithMaxCachedSchemas), evicting the oldest entry on overflow.
  • Context-aware — all evaluation methods accept context.Context for cancellation and timeouts. Long-running expressions check context at loop boundaries.
  • Linear-time regex — uses Go's standard regexp (RE2 engine) for guaranteed linear-time matching with no timeouts or backtracking.
  • 1,778 test cases — ported from the official jsonata-js test suite (0 failures, 0 skips).
  • One dependencytidwall/gjson for fast-path byte-level field extraction.
  • ~13K lines of Go — complete implementation with no code generation.
  • WASM support — compile to WebAssembly for an in-browser playground.

Quick Start

package main

import (
    "context"
    "fmt"
    "github.com/recolabs/gnata"
)

func main() {
    expr, _ := gnata.Compile(`Account.Order.Product.Price`)

    data := map[string]any{
        "Account": map[string]any{
            "Order": []any{
                map[string]any{"Product": map[string]any{"Price": 34.45}},
                map[string]any{"Product": map[string]any{"Price": 21.67}},
            },
        },
    }

    result, _ := expr.Eval(context.Background(), data)
    fmt.Println(result) // [34.45 21.67]
}

API Overview

gnata provides three evaluation tiers, each building on the previous:

Tier 1 — Eval(context.Context, any)

Evaluate against pre-parsed Go values. Pass a context for cancellation and timeouts.

expr, err := gnata.Compile(`$sum(orders.amount)`)
result, err := expr.Eval(ctx, data) // data is map[string]any

Tier 2 — EvalBytes(context.Context, json.RawMessage)

Evaluate directly against raw JSON bytes. For fast-path-eligible expressions, fields are extracted via GJSON with zero-copy — the entire document is never materialized.

expr, _ := gnata.Compile(`user.email = "admin@example.com"`)
result, _ := expr.EvalBytes(ctx, rawJSON) // rawJSON is json.RawMessage
fmt.Println(expr.IsFastPath())            // true — zero-copy evaluation

Tier 3 — StreamEvaluator

Batch-evaluate multiple compiled expressions against each event in a streaming pipeline. Schema-keyed plan caching deduplicates field extraction across expressions. Lock-free after warm-up.

se := gnata.NewStreamEvaluator(nil,
    gnata.WithPoolSize(500),
    gnata.WithMaxCachedSchemas(50000),
)

// Compile expressions (goroutine-safe)
indices := make([]int, len(rules))
for i, rule := range rules {
    indices[i], _ = se.Compile(rule.Expr)
}

// Hot path — millions of times, hundreds of goroutines
results, _ := se.EvalMany(ctx, eventBytes, schemaKey, indices)
for i, result := range results {
    if result != nil {
        handleMatch(indices[i], result)
    }
}

// Alternative: pre-decoded map input (avoids re-serialization)
results, _ = se.EvalMap(ctx, fieldMap, schemaKey, indices)

Custom Function Registration

Register domain-specific functions via WithCustomFunctions on StreamEvaluator or NewCustomEnv for standalone expressions. See Custom Functions below.

Streaming API

The StreamEvaluator is designed for high-throughput event processing where the same expressions are evaluated against millions of structurally similar events.

How It Works

Startup (once)
  Compile N expressions ──> Analyze AST: classify fast/full
  Configure stable routing: schemaKey -> exprIndices

Hot Path (millions/day, lock-free)
  Raw json.RawMessage event + schemaKey
    ├── BoundedCache lookup (atomic pointer read)
    │   ├── HIT  ──> Immutable GroupPlan
    │   └── MISS ──> Build plan (merge GJSON paths, atomic CAS store)
    ├── gjson.GetBytes per fast-path expression
    ├── Fast-path expressions: distribute extracted results (0 allocs)
    ├── Full-path expressions: selective unmarshal + AST eval
    └── results[]

Key Properties

  • Efficient JSON field extraction — fast-path expressions use gjson.GetBytes for zero-copy path lookups directly on raw JSON bytes.
  • Schema-keyed caching — the GroupPlan (merged paths, expression groupings, selective unmarshal targets) is computed once per schema key and reused immutably.
  • Lock-free readsBoundedCache publishes an atomic.Pointer snapshot on every write; reads scan the snapshot without acquiring a lock. Writes are serialised by a mutex.
  • Selective unmarshal — full-path expressions unmarshal only the subtrees they need (e.g., just the items array from a 10KB event), not the entire document.
  • Pre-decoded map inputEvalMap accepts map[string]json.RawMessage directly, skipping full-document serialization when the caller already has individually-encoded fields. Fast paths resolve top-level keys via O(1) map lookup.
  • Dynamic mutationReplace, Remove, and Reset methods allow modifying registered expressions at runtime with automatic cache invalidation.
  • Observability — implement MetricsHook to receive per-evaluation callbacks for cache hits/misses, eval latency, fast-path usage, and errors.

Custom Functions

gnata supports registering user-defined functions that extend the standard JSONata library.

Defining Custom Functions

Custom functions implement the CustomFunc signature:

type CustomFunc func(args []any, focus any) (any, error)

Where args are the evaluated arguments passed by the JSONata expression and focus is the current context value.

Registration

Register custom functions via WithCustomFunctions when creating a StreamEvaluator:

customFuncs := map[string]gnata.CustomFunc{
    "md5": func(args []any, focus any) (any, error) {
        if len(args) == 0 || args[0] == nil {
            return nil, nil
        }
        h := md5.Sum([]byte(fmt.Sprint(args[0])))
        return fmt.Sprintf("%x", h), nil
    },
    "parseEpochSeconds": func(args []any, focus any) (any, error) {
        // Convert epoch seconds to ISO 8601
        f, ok := args[0].(float64)
        if !ok {
            return nil, nil
        }
        return time.Unix(int64(f), 0).UTC().Format(time.RFC3339), nil
    },
}

se := gnata.NewStreamEvaluator(nil,
    gnata.WithCustomFunctions(customFuncs),
    gnata.WithMaxCachedSchemas(10000),
)

// Expressions can now use $md5() and $parseEpochSeconds()
idx, _ := se.Compile(`$md5(user.email)`)
result, _ := se.EvalOne(ctx, eventJSON, "schema1", idx)

Standalone Expression Evaluation

For one-off evaluations with custom functions, create a custom environment and pass it to EvalWithCustomFuncs:

env := gnata.NewCustomEnv(customFuncs)
expr, _ := gnata.Compile(`$md5(payload.email)`)
result, _ := expr.EvalWithCustomFuncs(ctx, data, env)

The environment should be created once and reused across evaluations for best performance.

Metrics & Observability

The StreamEvaluator accepts an optional MetricsHook (via WithMetricsHook) for production telemetry. Implement the interface and wire it in — a nil hook (the default) adds zero overhead.

Callback Arguments What to monitor
OnEval exprIndex, fastPath, duration, err Fast-path ratio; per-expression latency
OnCacheHit schemaKey Cache hit rate — should approach 100% after warm-up
OnCacheMiss schemaKey Cache misses trigger plan rebuilds
OnEviction Cache at capacity, evicting plans; increase WithMaxCachedSchemas

For point-in-time cache stats without a hook, use se.Stats() which returns hit/miss/entry/eviction counts.

Performance

All benchmarks on Apple M4 Pro. gnata is compared against the reference jsonata-js implementation running in Node.js. The JSONata (eval) column estimates pure evaluation time by subtracting RPC overhead from the total; entries showing < 1 us mean the expression evaluated faster than the measurement floor.

Fast Path (GJSON zero-copy)

Simple field lookups and comparisons are evaluated directly against raw json.RawMessage via GJSON — the JSON document is never fully parsed.

Expression gnata JSONata (eval) JSONata (RPC) Speedup
field.lookup 55 ns 83 us 232 us 1,500x
nested.3.deep 95 ns 56 us 205 us 590x
field = "string" 42 ns 49 us 197 us 1,170x
field = 2 (numeric) 142 ns 163 us 311 us 1,150x
field = true (bool) 111 ns 64 us 212 us 570x
field != null 41 ns 23 us 172 us 570x
field != "value" 41 ns < 1 us 147 us

Fast-path expressions typically achieve 0-2 allocations and 0-40 bytes per evaluation.

Function fast path

Expressions calling a supported built-in function on a pure path (e.g. $exists(a.b), $lowercase(name), $contains(path, "literal")) are classified at compile time. At runtime, the field is extracted with a single gjson.GetBytes call and the function is applied directly — no json.Unmarshal, no AST walk.

Supported functions (21): $exists, $contains, $string, $boolean, $number, $keys, $distinct, $not, $lowercase, $uppercase, $trim, $length, $type, $abs, $floor, $ceil, $sqrt, $count, $reverse, $sum, $max, $min, $average.

Boolean Logic

Expression gnata JSONata (eval) JSONata (RPC) Speedup
a = "x" and b = "y" 573 ns 24 us 173 us 43x
a = "x" or a = "y" 144 ns 38 us 187 us 270x
$not(field) 127 ns 7 us 156 us 58x
(a or b) and c 194 ns 17 us 166 us 88x
a and b and c (3-way) 180 ns 11 us 159 us 59x

Numeric & Arithmetic

Expression gnata JSONata (eval) JSONata (RPC) Speedup
field > 1 191 ns < 1 us 148 us
(field + 1) * 10 200 ns 12 us 160 us 58x
field in [1, 2, 3] 530 ns 17 us 165 us 32x
$sum(array) 177 ns < 1 us 144 us
$average(array) 190 ns < 1 us 143 us
$max(a) - $min(a) 264 ns 52 us 201 us 200x

String Functions

Expression gnata JSONata (eval) JSONata (RPC) Speedup
$uppercase(field) 117 ns < 1 us 139 us
$contains(field, "sub") 73 ns < 1 us 111 us
$split(email, "@") 247 ns 90 us 238 us 360x
$join(array, ", ") 233 ns < 1 us 140 us
a & "-" & b (concat) 580 ns 4 us 153 us 7x
$replace(field, "x", "y") 234 ns < 1 us 142 us
$uppercase($substringBefore(...)) 314 ns < 1 us 140 us

Array & Filtering

Expression gnata JSONata (eval) JSONata (RPC) Speedup
$count(array) 406 ns 13 us 162 us 33x
items[active = true].name 551 ns 13 us 162 us 24x
items[value > 20].name 573 ns 39 us 188 us 68x
$count(items[active]) 378 ns 42 us 190 us 110x
items.name (auto-map) 247 ns 34 us 183 us 140x
items^(value).name (sort) 524 ns 68 us 216 us 130x
items^(>value).name

Extension points exported contracts — how you extend this code

MetricsHook (Interface)
MetricsHook receives evaluation telemetry from StreamEvaluator. Implementations must be safe for concurrent use. All met [1 …
stream.go
CustomFunc (FuncType)
CustomFunc is a user-defined function that can be registered with gnata. It receives evaluated arguments and the current
gnata.go
EvalFn (FuncType)
EvalFn is a callback used by higher-order functions to invoke a lambda or builtin function value without creating an imp
functions/register.go
BuiltinFunction (FuncType)
BuiltinFunction is a native Go function implementing a JSONata built-in. args are the evaluated arguments; focus is the
internal/evaluator/env.go
GnataConfig (Interface)
(no doc)
npm/src/types.ts
StreamOption (FuncType)
StreamOption configures a StreamEvaluator.
stream.go
EnvAwareBuiltin (FuncType)
EnvAwareBuiltin is a built-in function that receives the current evaluation environment. This is required for functions
internal/evaluator/env.go

Core symbols most depended-on inside this repo

Eval
called by 62
internal/evaluator/evaluator.go
advance
called by 56
internal/parser/parser.go
Bind
called by 37
internal/evaluator/env.go
String
called by 36
internal/evaluator/eval_regex.go
ToFloat64
called by 35
internal/evaluator/value.go
expression
called by 34
internal/parser/parser.go
parseError
called by 32
internal/parser/parser.go
CollapseSequence
called by 32
internal/evaluator/value.go

Shape

Function 504
Method 109
Struct 46
FuncType 7
Class 4
TypeAlias 3
Interface 2

Languages

Go91%
TypeScript9%

Modules by API surface

wasm_exec.js51 symbols
stream.go31 symbols
gnata.go29 symbols
func_fast.go28 symbols
functions/string_format_integer.go26 symbols
internal/evaluator/ordered_map.go24 symbols
internal/evaluator/path.go23 symbols
internal/parser/parser.go22 symbols
functions/datetime_format.go22 symbols
internal/evaluator/env.go21 symbols
functions/string_format_number.go21 symbols
functions/datetime_parse.go21 symbols

For agents

$ claude mcp add gnata \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact