Tinox

A compiled, object-oriented programming language with Java-like syntax, LLVM backend, integrated HTTP server, and complete IDE tooling – designed for high-performance backend services.

Compiled LLVM Object-Oriented REST-API LSP High-Performance

What is Tinox?

Tinox is an independently developed, statically typed programming language that compiles directly to native machine instructions. The language combines a Java-like, annotation-driven syntax with the performance advantages of an LLVM-based compiler and a runtime system written in C.

The main focus is on the simple development of high-performance HTTP REST services: a controller handler requires only a few lines of code, and the built-in HTTP server is based on epoll and SO_REUSEPORT – technologies also used in production-ready web servers such as Nginx.

Core Idea: The productivity of a framework like Spring Boot or Quarkus – without JVM overhead, garbage collector pauses, or high memory usage.
import tinox.core.http_server;
import tinox.core.json;

class SortController
{
    @POST
    @Path("/sort")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    fn sort(ctx: HttpContext) -> Nothing
    {
        let root: JsonValue = Json.parse(ctx.request.body);
        let numbers: List<Int64> = Json.intArrayFromJson(root.getField("numbers"));
        ctx.response.body = Json.intArrayWrap("sorted", numbers.sort());
    }
}

This code represents a fully functional REST endpoint – no framework configuration overhead, no XML files, no boilerplate.

Technical Fundamentals

LLVM as Compiler Backend

The Tinox compiler does not generate native machine instructions directly, but rather LLVM Intermediate Representation (LLVM IR) – a platform-independent, typed assembly language. The LLVM toolchain then handles the optimization and translation to machine code.

Advantage: Tinox automatically benefits from all LLVM optimizations (loop unrolling, inlining, dead code elimination, link-time optimization) without needing to implement its own optimization logic.

LLVM is also used by Clang (C/C++), Rust, Swift, Julia, and Kotlin Native – it is one of the most mature compiler backends in the industry.

Rust as Implementation Language

The entire compiler – lexer, parser, type checker, code generator, and LSP server – is implemented in Rust. This brings the following advantages:

C as Runtime System

The runtime system (runtime.c) is written in C and provides all operating system-level functions: memory management, strings, arrays, maps, file I/O, networking, and the HTTP server. C functions are called directly from LLVM IR – no ABI overhead through wrapper layers.

Key Concepts

🔤

Static Typing

All types are checked at compile time. No runtime type errors possible.

🔗

AOT Compilation

Ahead-of-Time: the source code is fully compiled to a native binary before execution. No interpreter, no JIT.

♻️

Boehm GC

Tinox uses the Boehm Garbage Collector – a conservative, thread-safe GC. Memory is freed automatically; the developer does not need to manage anything manually.

🧵

1:1 Threading

Every spawn call creates a real OS thread (pthread). Full parallelism on multiple CPU cores.

Components Overview

Tinox consists of several Rust crates (libraries) that together form the compiler and tooling, plus the C runtime system and the standard library written in Tinox itself.

Component Language Purpose Size
tinox-commonRustShared data types (Span, Error, AST nodes)~250 lines
tinox-lexerRustTokenization (Lexer)~800 lines
tinox-parserRustAST construction + code formatter~2,500 lines
tinox-typecheckRustType checking + annotation processing~2,000 lines
tinox-codegenRustAST → LLVM IR~6,000 lines
tinox (CLI)RustCompiler control, REPL, project manager~1,700 lines
tinox-lspRustLanguage Server Protocol Server~1,000 lines
tinox-eclipseJavaEclipse IDE Plugin~400 lines
runtime.cCRuntime system: I/O, HTTP, memory, strings~1,700 lines
tinox-coreTinoxStandard library (55+ modules)~200 KB

Compiler Pipeline

The Tinox compiler transforms a .tnx source file in several steps into an executable native binary:

1

Lexer (tinox-lexer)

Reads the UTF-8 source text and breaks it into tokens (smallest meaningful units): keywords, identifiers, literals, operators, brackets. Each token carries precise position information (line, column, byte offset) for later error messages. Supports string interpolation ("Hello ${name}!").

2

Parser (tinox-parser)

Converts the token sequence into an Abstract Syntax Tree (AST) – a tree structure representing the syntactic structure of the program. Recursive descent parser: every grammar rule corresponds to a function. Recognizes classes, methods, expressions, loops, match expressions, etc.

3

Import Resolution (tinox CLI)

import declarations are resolved recursively: the imported .tnx files (from the standard library or the project directory) are loaded, parsed, and embedded into the AST. The result is a single, consolidated AST with all required declarations.

4

Type Checker (tinox-typecheck)

Two-pass approach: first all declarations (classes, methods, functions) are registered, then expressions and statements are checked for type correctness. Also validates annotations (@GET, @POST, @Produces etc.), extracts REST routes, and recognizes DI components.

5

Code Generator (tinox-codegen)

Traverses the AST directly and generates LLVM IR as text. Generates vtable structures for interface dispatch, monomorphizes generic functions/classes, allocates closure environments, generates HTTP route handlers. No intermediate step – directly AST → IR.

6

llc (LLVM Compiler)

The external LLVM tool llc translates LLVM IR into assembler. In release mode with -O3, extensive optimizations (inlining, loop optimizations, vectorization) are performed.

7

Clang (C Compiler / Linker)

clang compiles runtime.c into an object file and links it with the generated assembler into a finished native binary. This also links -lm (math library).

Result: A single, statically linked binary with no external dependencies (except the C standard library). Startup time: <1ms.

Runtime (C Runtime System)

The core of the Tinox runtime system is runtime/runtime.c (~1,700 lines of C code). It provides all primitives that the generated LLVM IR accesses directly.

Memory Management

Tinox uses the Boehm Garbage Collector (libgc) for automatic memory management. The GC is conservative: it scans the stack and registers for pointers and frees objects that are no longer reachable – without the developer having to call free() explicitly. With GC_THREADS it is fully thread-safe and works correctly with the epoll-based HTTP server thread pool.

For frequent operations (sort, JSON parsing), thread-local static buffers that are allocated once and then reused – this completely eliminates malloc overhead in the request-critical path.

Collections

TypeImplementationSpecial Feature
ArrayDynamic C arrayLength stored at offset -1; grows on push
MapOpen-addressing hash tableFNV-1a hash, load factor 0.75, tombstones for delete
StringC string (char*)Immutable; operations create new object

Async & Concurrency

The concurrency model is based on POSIX threads (pthreads). spawn creates a real OS thread. Channels internally use a linked-list queue with mutex and condition variable.

HTTP Server (Low Level)

The HTTP server in the runtime uses:

Type System

Primitive Types

Tinox TypeBitLLVM-IRDescription
Int8 / Int16 / Int32 / Int648–64i8–i64Signed integers
UInt8 / UInt16 / UInt32 / UInt648–64i8–i64Unsigned integers
Float32 / Float6432/64float/doubleIEEE 754 floating-point numbers
Bool1i1Boolean value
Char32i32Unicode code point
Stringi8*UTF-8 string (C string)
NothingvoidNo return value (equivalent to void)
NevervoidFunction never returns (throw, exit)

Composite Types

let numbers: List<Int64> = [1, 2, 3];                 // Array
let ages: Map<String, Int32> = @{"Alice" => 30};  // Map/Dictionary
let pair: (Int32, String) = (42, "hello");           // Tuple
let name: String? = null;                             // Nullable
let fn add: fn(Int32, Int32) -> Int32 = |a, b| a + b; // Function type

Generics

Tinox supports parametric polymorphism via monomorphization: a separate function is generated for each concrete type – like C++ templates or Rust. There is no runtime overhead from type erasure (as in Java).

fn max<T>(a: T, b: T) -> T
{
    if a > b { return a; }
    return b;
}

class Box<T>
{
    var value: T;
}

Object Orientation

Tinox is a hybrid language – object-oriented with classes, inheritance, and interfaces, but procedural free functions and functional concepts (lambdas, higher-order functions) are also possible.

Classes

class Animal
{
    var name: String;
    var age:  Int32;

    fn speak() -> String        // instance method
    {
        return "...";
    }

    fnc create(name: String) -> Animal  // static method (fnc)
    {
        return Animal { name: name, age: 0 };
    }
}

class Dog extends Animal       // inheritance
{
    fn speak() -> String
    {
        return "Wuff!";
    }
}

Interfaces & Polymorphism

interface Serializable
{
    fn serialize() -> String;
}

class User implements Serializable
{
    var name: String;

    fn serialize() -> String
    {
        return "User:" + this.name;
    }
}

Interface dispatch is implemented via vtables – identical to C++ virtual functions. Every object that implements an interface receives a pointer to a table of function pointers.

Keyword Overview

KeywordMeaning
fnInstance method (has implicit this)
fncStatic method (no this, called via class name)
letImmutable local variable
varMutable local variable or field
classClass declaration
interfaceInterface declaration (like Java interface)
enumEnumeration type
extendsSingle inheritance
implementsInterface implementation
spawnStart a new thread
awaitWait for thread result
deferExecute code when leaving the scope
matchPattern matching expression

Annotations

The annotation system is one of the core strengths of Tinox. Annotations control the behavior of the compiler, the HTTP server, and future frameworks – similar to Java/Spring or Quarkus.

HTTP REST Annotations

@GET
@Path("/users/:id")
@Produces(MediaType.APPLICATION_JSON)
@Auth("bearer")
@StatusCode(200)
fn getUser(ctx: HttpContext) -> Nothing { ... }

Serialization

@JsonSerializable
class User
{
    var id:   Int64;

    @JsonField("first_name")   // Overrides the JSON key
    var firstName: String;

    @Sensitive              // Output as "***" in logs
    var password: String;

    @DoNotSerialize         // Excluded from JSON
    var internalToken: String;
}

All Built-in Annotations

AnnotationTargetDescription
@GET @POST @PUT @PATCH @DELETEMethodHTTP verb for REST route
@Path("...")Class / MethodURL path (supports :param)
@Produces(MediaType.*)MethodResponse content type
@Consumes(MediaType.*)MethodRequest content type
@StatusCode(200)MethodDefault HTTP status code
@Auth("bearer")Class / MethodAuthentication required
@JsonSerializableClassGenerates toJson()/fromJson()
@JsonField("name")FieldAlternative JSON key
@SensitiveFieldCompletely masked in logs
@MaskedFieldPartially masked in logs
@DoNotSerializeFieldExclude field from JSON
@LogClassAutomatically inject log field
@InjectFieldDependency injection field
@Config("key")FieldValue from application.properties
@ApplicationComponentClassSingleton scope for DI
@HttpRequestScopedClassNew object per HTTP request
@Test("desc")MethodUnit test (tinox test)
@inlineFunction / MethodForced LLVM inlining
@deprecated("msg")AllDeprecation warning
@Command("name")ClassCLI command
@annotationClassDefine custom annotation
@WebsocketEndpoint("/path"[, port])ClassWebSocket: generates an accept/message loop as main (only with exactly one endpoint class and no own main)
@OnOpenMethodWebSocket: called on new connection, signature fn(conn: Int64) -> Nothing
@OnMessageMethodWebSocket: called per text message, signature fn(conn: Int64, msg: String) -> Nothing. AMQP-1.0: called per received message, signature fn(msg: Amqp10Message) -> Nothing. AMQP-0-9-1: called per received message, signature fn(msg: AmqpMessage091) -> Nothing
@OnCloseMethodWebSocket: called when the connection ends, signature fn(conn: Int64) -> Nothing
@Amqp10Consumer(host, port, user, pass, address)ClassAMQP-1.0: generates a connect/attach/receive loop as main (only with exactly one consumer class and no own main)
@Amqp091Consumer(host, port, vhost, user, pass, queue)ClassAMQP-0-9-1: generates a connect/open/qos/consume/receive loop as main (only with exactly one consumer class and no own main); prefetch is fixed at 1 in v1

Standard Library (tinox-core)

The standard library consists of over 55 modules written in Tinox itself. Import: import tinox.core.<module>;

HTTP & Web

http_server, rest, rest_framework, http, websocket – complete HTTP/1.1 server with routing, middleware, and content negotiation, plus a WebSocket server per RFC 6455.

Messaging

amqp091 – AMQP-0-9-1 client (no broker) for message-queue brokers like RabbitMQ: connect, channel, publish/consume. amqp10 – standalone AMQP-1.0 client with a connection/session/link hierarchy and credit-based flow control.

Data Formats

json, xml, csv, toml, ini, base64, hex – serialization and deserialization of common formats.

Mathematics

math, mathf, decimal, complex – trigonometric functions, high-precision arithmetic, complex numbers.

Collections

array, sort, set, queue, stack, heap, graph, trie – algorithms and data structures.

Strings & Regex

string, regex, fmt, tpl – string manipulation, regular expressions, formatting, templating.

I/O & System

io, fs, env, process, socket – file system, environment variables, processes, low-level networking.

Concurrency

cron, events, pool, semaphore, ratelimit, cache – scheduling, event bus, thread pool, synchronization.

Cryptography

crypto, jwt, bcrypt – hashing, JSON Web Tokens, password hashing.

LSP – Basics

The Language Server Protocol (LSP) is an open protocol developed by Microsoft in 2016 that standardizes the communication between a language server (which understands the programming language) and an editor (which shows the user interface).

The Problem Before LSP

Previously, every IDE had to implement its own plugins for every language: Eclipse plugin for Java, VS Code plugin for Java, IntelliJ plugin for Java – all separate implementations of the same logic. With n languages and m editors, n × m integrations were created.

LSP Solution: n + m Instead of n × m

With LSP you write one language server, and every LSP-compatible editor uses it automatically. The language server communicates via JSON-RPC over stdio with the editor.

Editor (Eclipse, VS Code, Neovim …)
      │
      │  JSON-RPC via stdin/stdout
      ▼
Language Server (tinox-lsp Binary)
      │
      │  Analyzes .tnx files
      ▼
Lex → Parse → Typecheck → Responses to editor

Important LSP Methods

LSP MethodWhat the editor sendsWhat the server responds
textDocument/hoverCursor positionMarkdown with type information
textDocument/completionCursor position after .List of completion suggestions
textDocument/definitionCursor on identifierFile + line of the definition
textDocument/documentSymbolDocument URIHierarchical symbol list (Outline)
textDocument/publishDiagnostics– (server push)Error positions + messages
textDocument/didChangeNew file contentTriggers re-analysis
Advantage: The tinox-lsp language server works in Eclipse, VS Code, Neovim, Emacs, and every other LSP-capable editor – without language-specific plugin development per editor.

Tinox LSP Server

tinox-lsp is a complete LSP server implemented with tower-lsp 0.20 (Rust) and Tokio as asynchronous runtime.

Embedded Standard Library

All 26+ standard library modules are embedded directly into the LSP binary using include_str!(). The server requires no installation or configuration of file paths – it works immediately after copying the binary.

Import Resolution for Diagnostics

When a .tnx file is opened, the LSP server automatically resolves import declarations and loads the corresponding stdlib ASTs as prelude. This way Json.parse() or HttpContext are not marked as "undefined".

Dot Completion for Classes

When the user types Json.:

  1. The editor sends textDocument/completion with the offset after the dot
  2. The LSP extracts the dot chain (["Json"])
  3. It checks whether Json is a variable (instance) or a class name
  4. If class name: directly load the methods of Json from the registry
  5. Static methods (fnc) are displayed with the hint fnc parse(...)

Technical Details

PropertyValue
ProtocolLanguage Server Protocol 3.17 (JSON-RPC via stdio)
Async RuntimeTokio 1.x (multi-threaded)
Document StorageDashMap (lock-free concurrent HashMap)
Stdlib embeddedYes, via include_str!() at compile time
Diagnostics ModeFull re-parse + typecheck bei jedem save/change
Supported EditorsEclipse (LSP4E), VS Code, Neovim, Emacs, …

Eclipse Plugin

The Eclipse plugin tinox-eclipse connects the Eclipse IDE with the tinox-lsp language server via the LSP4E framework – the LSP client for Eclipse.

Features

🔴

Real-Time Error Highlighting

Syntax errors and type errors are underlined directly in the editor as soon as the file is saved.

💡

Hover Information

Mouse over a function or variable: type signature and documentation appear in the tooltip.

🔍

Go to Definition

Ctrl+click or F3 jumps to the definition of a class, method, or function.

📝

Code Completion

Ctrl+Space or after typing a dot, method and field suggestions appear.

📋

Outline View

The Eclipse Outline View shows all classes, methods, and fields of the current file hierarchically.

▶️

Run (Ctrl+F11)

Direct compilation and execution of the current .tnx file from within Eclipse.

Architecture

The plugin is an OSGi bundle that registers the .tnx file extension via plugin.xml and instructs LSP4E to start the tinox-lsp process when .tnx files are opened. Communication runs over stdin/stdout in JSON-RPC.

CLI Tools

The tinox binary is the central tool for all development tasks:

CommandDescription
tinox new <name>Scaffold new project (folder structure, tinox.toml, main.tnx)
tinox build <file>Compile to native binary (release: -O3 + LTO)
tinox run <file>Compile and run immediately
tinox dev <file>Hot-reload mode: automatically recompile on file change
tinox check <file>Type check only, no binary generated
tinox test <file>Run all @Test-annotated methods
tinox fmt <file>Format source code (AST → formatted text)
tinox docGenerate HTML documentation from docstrings
tinox replInteractive REPL session
tinox add …Add package dependency
tinox installDownload and install dependencies

HTTP Server & Performance

The integrated HTTP server is optimized for maximum throughput at minimal latency. It uses technologies also employed in production-ready systems such as Nginx and Envoy.

epoll – Event Multiplexing

Instead of blocking a thread for each connection, the server registers all active connections in an epoll file descriptor. One epoll_wait() call delivers all connections that have readable data – in a single system call.

// Concept (simplified):
epoll_fd = epoll_create1(0);
// Register all active connections...
while (true)
{
    events = epoll_wait(epoll_fd, max_events, 50); // 50ms Timeout
    for event in events
    {
        if event.fd == server_socket { accept(); }
        else { handle_request(event.fd); }
    }
}

SO_REUSEPORT – Kernel Load Balancing

With SO_REUSEPORT, multiple threads (or processes) can listen on the same port simultaneously. The Linux kernel automatically distributes incoming connections to available listeners – without locks, without bottlenecks.

Thread-Local Buffers

Each HTTP worker thread has its own static buffer for request data, response data, and JSON parsing. These buffers are never freed and are reused – this completely eliminates malloc/free in the critical request path.

Result: Up to 4,096 simultaneous connections per thread, linear scaling across CPU cores via SO_REUSEPORT.

Benchmark: Tinox vs. Quarkus

A sort REST endpoint was measured under identical conditions against Quarkus (Java, native image):

Metric Tinox Quarkus Difference
Throughput 26.700–27.000 req/s 24.867–25.541 req/s +5–7%
Latency (mean) 0,44–0,45 ms 0,52–0,55 ms ~17–20% better
Error rate 0 % 0 %

Test conditions: 5,000 requests, concurrency 20, list with 20 numbers per request, Linux x86_64.

Note: Quarkus was compiled with native-image – thus also without JVM overhead. The comparison is therefore fair: both systems run as native Linux binaries.

Pros & Cons

✓ Advantages

  • Very low latency and high throughput thanks to LLVM + epoll
  • Boehm GC – automatic memory management without borrow-checker complexity
  • Low startup time (<1ms) – ideal for serverless/Lambda
  • Java-like syntax – low entry barrier for backend developers
  • Annotation-driven REST: minimal boilerplate
  • Complete tooling: LSP, Eclipse plugin, REPL, formatter
  • Standard library written in the language itself
  • Small binary footprint – no framework as runtime dependency
  • Embedded stdlib in the LSP – no configuration required
  • Cross-platform via LLVM (Linux, macOS, Windows possible)

✗ Disadvantages

  • Conservative GC (Boehm) – can be somewhat slower than manual management for pointer-intensive workloads
  • Small ecosystem – no mature third-party libraries
  • No standardized specification – language changes quickly
  • Compiler errors not yet as helpful as in Rust or Go
  • No complete module system with real separation between projects
  • No borrow-checker safety – use-after-free theoretically possible (but GC prevents leaks)
  • Long compile times due to LLVM (for large projects)
  • No Windows support (epoll is Linux-specific)
  • No stable releases yet – pre-1.0
  • Thin community and documentation so far

Comparison: Memory Safety

Tinox gives the developer full control over memory – like C –, but does not offer static guarantees like Rust's borrow checker. This means:

LanguageMemory ModelSafetyPerformance
Java / KotlinGCHigh (no UAF)GC pauses
RustOwnership + Borrow-CheckerVery high (compile-time)Maximum
GoGC (concurrent)HighGood
TinoxBoehm GC (conservative)GC prevents leaksVery high
C/C++ManualDeveloper responsibilityMaximum

Use Cases

🌐

High-Performance REST APIs

Microservices with very low latency requirements (trading, real-time systems). Annotation-driven routing, no framework overhead.

Serverless / Edge Computing

Cold start <1ms thanks to native binary without JVM or Node.js. Ideal for AWS Lambda, Cloudflare Workers (native mode), edge functions.

🔧

System Tools & CLIs

With @Command/@Option/@Argument CLI tools can be developed with minimal effort. Native binary without runtime dependencies.

📊

Data Processing Pipelines

Batch processing of large data volumes with automatic GC – no manual memory management required, yet high performance.

🔒

Security-Critical Services

Deterministic runtime behavior through conservative GC with predictable behavior. Annotations for sensitivity and masking built in.

🎓

Teaching & Research

Small, readable compiler codebase. Ideal for learning compiler construction, LLVM IR, and LSP development.

Less Suitable For…

Comparison with Other Languages

Feature Tinox Java / Quarkus Go Rust C
Compilation AOT → native JVM / AOT (native image) AOT → native AOT → native AOT → nativ
Garbage Collector Yes (Boehm GC) Yes (G1GC, ZGC) Yes (concurrent) No (Ownership) No
Memory Safety GC-protected (no leaks) GC-protected GC-protected Compile-time Manual
Syntax Style Java-like Java C-like C-like C
REST Annotations Built-in JAX-RS / Quarkus No standard No standard None
Startup Time <1ms JVM: 1–3s; Native: ~10ms ~5ms <1ms <1ms
IDE Tooling LSP + Eclipse Very mature Good (gopls) Very good (rust-analyzer) Medium (clangd)
Ecosystem Small (growing) Huge (Maven) Good Gut (crates.io) Large (mature)
Concurrency Threads + Channels Virtual Threads (Java 21) Goroutines async/await + Threads pthreads
Backend LLVM Yes No (JVM / Graal) No (custom) Yes Optional (clang)
Positioning: Tinox fills a niche between Go and C: more productive syntax than C/C++, without the GC overhead of Go or Java, with a built-in REST framework that approaches Spring Boot or Quarkus in terms of developer convenience – but surpasses them in performance.

http_server

import tinox.core.http_server;

HTTP/1.1 server implementation for building RESTful APIs. Supports route registration for all common HTTP methods (GET, POST, PUT, PATCH, DELETE), middleware chains, and automatic parsing of requests and responses. Path parameters are declared via the :param syntax, wildcard segments via *.

Classes & Methods

Class / MethodSignatureDescription
HttpServer::newfn new(port: Int64) -> HttpServerCreates a new server listening on the specified port.
HttpServer::getfn get(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServerRegisters a GET route handler. Returns this (chaining).
HttpServer::postfn post(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServerRegisters a POST route handler.
HttpServer::putfn put(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServerRegisters a PUT route handler.
HttpServer::patchfn patch(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServerRegisters a PATCH route handler.
HttpServer::deletefn delete(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServerRegisters a DELETE route handler.
HttpServer::usefn use(mware: fnc(HttpContext) -> Bool) -> HttpServerAdds a middleware function. If the middleware returns false, the request is aborted.
HttpServer::listenfn listen() -> NothingStarts the server (blocking call). Only terminates via stop().
HttpServer::stopfn stop() -> NothingStops the server loop.
HttpServer::handleRequestfn handleRequest(ctx: HttpContext, clientFd: Int64) -> NothingProcesses an incoming request through middleware and route matching.
HttpServer::statusTextfn statusText(code: Int64) -> StringReturns the HTTP status text for a numeric status code (e.g. 200 → "OK").
HttpRequest::newfn new(method: String, path: String, queryString: String, headers: Map<String, String>, body: String) -> HttpRequestCreates a new HTTP request instance.
HttpRequest::getHeaderfn getHeader(name: String) -> StringReturns the value of a request header (case-insensitive).
HttpRequest::getParamfn getParam(name: String) -> StringReturns a path parameter extracted by :name in the route pattern.
HttpRequest::getQueryfn getQuery(name: String) -> StringReturns a query string parameter.
HttpRequest::jsonfn json() -> JsonValueParses the request body as JSON.
HttpRequest::bodyAsfn bodyAs<T>() -> TDeserializes the body into an object of type T (must be @JsonSerializable).
HttpRequest::textfn text() -> StringReturns the body as a raw string.
HttpRequest::isContentTypefn isContentType(contentType: String) -> BoolChecks whether the Content-Type header matches the given value.
HttpResponse::newfn new() -> HttpResponseCreates a new response with default status code 200.
HttpResponse::statusfn status(code: Int64) -> HttpResponseSets the HTTP status code. Returns this (chaining).
HttpResponse::headerfn header(name: String, value: String) -> HttpResponseSets a response header.
HttpResponse::textfn text(text: String) -> HttpResponseSets the response body as plain text (text/plain).
HttpResponse::jsonfn json(json: String) -> HttpResponseSets the response body as a JSON string (application/json).
HttpResponse::jsonObjectfn jsonObject<T>(obj: T) -> HttpResponseSerializes a @JsonSerializable object and sets it as the JSON body.
HttpResponse::htmlfn html(html: String) -> HttpResponseSets the response body as HTML (text/html).
HttpResponse::notFoundfn notFound(message: String) -> HttpResponseSets status code 404 and an error message.
HttpResponse::badRequestfn badRequest(message: String) -> HttpResponseSets status code 400 and an error message.
HttpResponse::internalErrorfn internalError(message: String) -> HttpResponseSets status code 500 and an error message.
HttpResponse::createdfn created(json: String) -> HttpResponseSets status code 201 with JSON body.
HttpResponse::noContentfn noContent() -> HttpResponseSets status code 204 (no body).
HttpResponse::redirectfn redirect(url: String) -> HttpResponseSets a 302 redirect with Location header.
HttpResponse::corsfn cors(origin: String) -> HttpResponseSets CORS headers for cross-origin requests.
HttpContext::newfn new(request: HttpRequest, response: HttpResponse) -> HttpContextCreates a request context that encapsulates request and response.
RouteMatcher::matchesfn matches(pattern: String, path: String) -> BoolChecks whether a path matches a route pattern (incl. :param and *).
RouteMatcher::extractParamsfn extractParams(pattern: String, path: String) -> Map<String, String>Extracts path parameters from a concrete path using the pattern.
QueryString::parsefn parse(qs: String) -> Map<String, String>Parses a query string into a key-value map.
QueryString::getfn get(qs: String, name: String) -> StringReturns a single query parameter from a raw query string.

Example

import tinox.core.http_server;

// Auth middleware: checks Authorization header
let authMiddleware: fnc(HttpContext) -> Bool = ctx => {
    let token: String = ctx.request.getHeader("Authorization");
    if token.len() == 0 {
        ctx.response.status(401).text("Unauthorized");
        return false;
    }
    return true;
};

let server: HttpServer = HttpServer::new(8080);

server.use(authMiddleware);

server.get("/hello", ctx => {
    ctx.response.text("Hello, World!");
});

server.get("/users/:id", ctx => {
    let id: String = ctx.request.getParam("id");
    let page: String = ctx.request.getQuery("page");
    ctx.response.json("{\"id\": \"" + id + "\", \"page\": \"" + page + "\"}");
});

server.post("/users", ctx => {
    let data: JsonValue = ctx.request.json();
    ctx.response.status(201).json("{\"created\": true}");
});

server.delete("/users/:id", ctx => {
    ctx.response.noContent();
});

server.listen();

http2_server

import tinox.core.http2_server;

Complete HTTP/2 server implementation according to RFC 7540 with binary framing, HPACK header compression, stream multiplexing, and flow control. The routing API is identical to http_server, so existing handlers can be reused without modification. Each connection goes through the complete HTTP/2 connection sequence: preface validation, SETTINGS exchange, frame processing loop, and GOAWAY shutdown.

Classes & Methods

Class / MethodSignatureDescription
Http2Server::newfn new(port: Int64) -> Http2ServerCreates a new HTTP/2 server on the specified port.
Http2Server::getfn get(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2ServerRegisters a GET route handler.
Http2Server::postfn post(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2ServerRegisters a POST route handler.
Http2Server::putfn put(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2ServerRegisters a PUT route handler.
Http2Server::patchfn patch(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2ServerRegisters a PATCH route handler.
Http2Server::deletefn delete(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2ServerRegisters a DELETE route handler.
Http2Server::usefn use(mware: fnc(HttpContext) -> Bool) -> Http2ServerAdds a middleware function.
Http2Server::listenfn listen() -> NothingStarts the server (blocking). Accepts and processes HTTP/2 connections.
Http2Server::stopfn stop() -> NothingStops the server loop.
Http2Server::handleConnectionfn handleConnection(fd: Int64) -> NothingManages a single HTTP/2 connection completely: preface, SETTINGS, frame loop, GOAWAY.
Http2Server::readFramefn readFrame(conn: Http2Conn) -> Http2FrameReads an HTTP/2 frame (9-byte header + payload) from the client.
Http2Server::writeFramefn writeFrame(conn: Http2Conn, frame: Http2Frame) -> NothingSends an HTTP/2 frame to the client.
Http2Server::sendServerSettingsfn sendServerSettings(conn: Http2Conn) -> NothingSends the server's initial SETTINGS frame (push disabled, max. 128 streams).
Http2Server::sendGoawayfn sendGoaway(conn: Http2Conn, errorCode: Int64) -> NothingSends a GOAWAY frame and closes the connection.
Http2Server::sendRstStreamfn sendRstStream(conn: Http2Conn, streamId: Int64, errorCode: Int64) -> NothingSends an RST_STREAM frame to abort a single stream.
Http2Server::processHeadersfn processHeaders(conn: Http2Conn, frame: Http2Frame) -> NothingProcesses a HEADERS frame and accumulates the HPACK header block.
Http2Server::processDatafn processData(conn: Http2Conn, frame: Http2Frame) -> NothingProcesses a DATA frame and accumulates body bytes.
Http2Server::dispatchStreamfn dispatchStream(conn: Http2Conn, stream: Http2Stream) -> NothingDecodes a complete stream into an HttpContext and runs middleware and route matching.
Http2Server::sendResponsefn sendResponse(conn: Http2Conn, streamId: Int64, response: HttpResponse) -> NothingEncodes an HttpResponse as HEADERS and DATA frames and sends it.
Http2Frame::newfn new(type: Int64, flags: Int64, streamId: Int64, payload: List<Int64>) -> Http2FrameCreates an HTTP/2 frame with header fields and payload.
Http2Conn::newfn new(handle: Int64) -> Http2ConnCreates the connection state for an HTTP/2 connection incl. HPACK tables.
Http2Stream::newfn new(id: Int64) -> Http2StreamCreates a new stream state for an HTTP/2 stream.
Http2FrameType::DATAfn DATA() -> Int64Frame type constant: DATA (0x0).
Http2FrameType::HEADERSfn HEADERS() -> Int64Frame type constant: HEADERS (0x1).
Http2FrameType::SETTINGSfn SETTINGS() -> Int64Frame type constant: SETTINGS (0x4).
Http2FrameType::GOAWAYfn GOAWAY() -> Int64Frame type constant: GOAWAY (0x7).
Http2Error::NO_ERRORfn NO_ERROR() -> Int64Error code: No error (0x0).
Http2Error::PROTOCOL_ERRORfn PROTOCOL_ERROR() -> Int64Error code: Protocol error (0x1).

Example

import tinox.core.http2_server;

let server: Http2Server = Http2Server::new(8443);

// Logging-Middleware
server.use(ctx => {
    print("Request: " + ctx.request.method + " " + ctx.request.path);
    return true;
});

server.get("/hello", ctx => {
    ctx.response.text("Hello via HTTP/2!");
});

server.post("/api/data", ctx => {
    let body: JsonValue = ctx.request.json();
    ctx.response.status(201).json("{\"ok\": true}");
});

server.get("/users/:id", ctx => {
    let id: String = ctx.request.getParam("id");
    ctx.response.json("{\"id\": \"" + id + "\"}");
});

server.delete("/users/:id", ctx => {
    ctx.response.noContent();
});

// Start blocking — processes HTTP/2 connections with
// full RFC-7540 framing, HPACK compression, and stream multiplexing
server.listen();

http3_server

import tinox.core.http3_server;

HTTP/3 (RFC 9114) over QUIC (RFC 9000). Unlike http_server (protocol parsed entirely in C) and http2_server (framing/ HPACK hand-rolled in pure Tinox), this module can't push protocol logic into Tinox: QUIC transport (loss recovery, congestion control, AEAD) and HTTP/3 framing + QPACK are implemented by the ngtcp2 and nghttp3 C libraries, whose callback-table APIs can't invoke back into a Tinox closure mid-callback. All QUIC/HTTP-3/QPACK state therefore lives in runtime.c behind opaque Int64 handles (the same pattern as the existing TLS connection handling) — this file only registers routes and pumps the native event loop, reusing the same Route/RouteMatcher/HttpRequest/ HttpResponse/HttpContext API as http_server/http2_server unmodified.

TLS is mandatory (QUIC has no plaintext mode) — certificate/key go directly to the constructor; unlike HttpServer there is no separate listen()/listenTls() split.

Build flag: requires the compiler to be run with TINOX_HTTP3=1 (opt-in, default OFF — unlike TINOX_TLS, which defaults ON: OpenSSL is nearly universally installed, ngtcp2/nghttp3 far less so; defaulting this on would break tinox build with a compile error on any system lacking them, instead of a clean runtime message). Also requires TLS to be enabled (ngtcp2_crypto_ossl needs OpenSSL underneath).

Known limitation (0-RTT): enableEarlyData(), HttpRequest.wasEarlyData, and the 425-Too-Early policy for non-idempotent early-data requests are wired end-to-end, but SSL_CTX_set_max_early_data itself is deliberately left unset under this OpenSSL 3.6.3 + ngtcp2_crypto_ossl 1.25.0 combination (setting it reproducibly caused a stall right after the TLS handshake completed; root cause not isolated in the time available). As a result the TLS layer never actually accepts 0-RTT data — a client attempting early data transparently falls back to a normal 1-RTT round trip, so wasEarlyData is currently always false. Not a crash, just an inactive feature for now.

Classes & Methods

Class / MethodSignatureDescription
Http3Server::newfn new(port: Int64, certPath: String, keyPath: String) -> Http3ServerCreates a new HTTP/3 server. TLS certificate/key are mandatory parameters.
Http3Server::getfn get(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3ServerRegisters a GET route handler.
Http3Server::postfn post(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3ServerRegisters a POST route handler.
Http3Server::putfn put(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3ServerRegisters a PUT route handler.
Http3Server::patchfn patch(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3ServerRegisters a PATCH route handler.
Http3Server::deletefn delete(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3ServerRegisters a DELETE route handler.
Http3Server::usefn use(mware: fnc(HttpContext) -> Bool) -> Http3ServerAdds a middleware function.
Http3Server::requireAddressValidationfn requireAddressValidation(v: Bool) -> Http3ServerControls the QUIC Retry mechanism (RFC 9000 §8.1, default true). false saves a round trip in local dev loops.
Http3Server::enableEarlyDatafn enableEarlyData(maxSize: Int64) -> Http3ServerOpts into TLS 1.3 0-RTT (default off). See the known limitation above.
Http3Server::listenfn listen() -> NothingBinds the UDP socket and starts the pump loop (blocking).
Http3Server::stopfn stop() -> NothingStops the server loop.
HttpRequest.wasEarlyDatavar wasEarlyData: BoolNew field on HttpRequest (see http_server): true if the request arrived as TLS 1.3 0-RTT early data. Always false for HttpServer/Http2Server.

Example

import tinox.core.http3_server;

let server: Http3Server = Http3Server::new(8443, "cert.pem", "key.pem");

server.get("/hello", ctx => {
    ctx.response.text("Hello via HTTP/3!");
});

server.post("/echo", ctx => {
    ctx.response.text("echo:" + ctx.request.body);
});

// Start blocking -- QUIC handshake, HTTP/3 framing, and QPACK are
// handled entirely by ngtcp2/nghttp3.
server.listen();

Build/run: TINOX_HTTP3=1 tinox run server.tnx, then e.g. curl --http3-only -k https://localhost:8443/hello (tested against real curl 8.21.0, linked against the same ngtcp2/nghttp3 versions).

A complete example (a JSON REST API with GET/POST/PUT/PATCH/DELETE) lives at examples/http3_rest_api.

@Http3RestController — declarative instead of the fluent API

The same @GET/@POST/@PUT/ @PATCH/@DELETE/@Path/ @StatusCode/@Produces/@Consumes/ @Auth/@OIDCRolesAllowed annotations already used by the plain (TCP) REST auto-server (module rest) also work for HTTP/3 — with one additional class annotation, @Http3RestController(port, certPath, keyPath), the compiler generates a complete Http3Server::new()/ .get()/.post()/…/.listen() `main`, exactly the wiring the hand-written fluent-API version above writes out by hand — not the GC-crash-prone TCP auto-server (tinox_HttpServer_listen, issue #140), but Http3Server directly.

At most one @Http3RestController class per program (hard error otherwise); every @GET/… route in the whole program ends up on that one server. Cannot be combined with @WebsocketEndpoint/@Amqp10Consumer/ @Amqp091Consumer (each generates its own auto-run `main`). Since the compiler allocates a fresh, zeroed controller instance per request by default, a stateful controller additionally needs @ApplicationComponent (a reused singleton instance via {Class}_di_get()) — see examples/http3_rest_api/src/TaskController.tnx for the full example and details.

import tinox.core.http3_server;

@ApplicationComponent
@Http3RestController(8843, "cert.pem", "key.pem")
class TaskController
{
    var tasks: List<Task>;

    @GET
    @Path("/tasks")
    fn listTasks(ctx: HttpContext) -> Nothing
    {
        ctx.response.json(Json::serialize(this.tasks));
    }
}

websocket

import tinox.core.websocket;

WebSocket server per RFC 6455, built on the HTTP server's conn-handle architecture. The textual handshake (upgrade request, Sec-WebSocket-Accept) runs over the existing C-string paths, the binary frames over length-based byte primitives so NUL bytes and masking are handled correctly. v1 is deliberately an explicit polling-loop API (no lambda handler) and serves one connection at a time per server (no epoll for WS).

Classes & Methods

Class / MethodSignatureDescription
WsServer::listenfn listen(port: Int64) -> Int64Opens the server socket. Returns the server handle (< 0 on bind failure, e.g. port already in use).
WsServer::acceptfn accept(srv: Int64) -> Int64Accepts the next connection and performs the RFC 6455 handshake. Returns the conn handle, or -1 if the handshake fails (the connection is already closed in that case).
WsServer::listenTlsfn listenTls(port: Int64, certPath: String, keyPath: String) -> Int64wss:// variant of listen. OpenSSL is linked by default; when built with TINOX_TLS=0 this function returns -1 with a stderr diagnostic instead of a link error.
WsServer::acceptTlsfn acceptTls(srv: Int64) -> Int64wss:// variant of accept: TLS handshake followed by the same RFC 6455 handshake over the same conn handle.
Ws::readMessagefn readMessage(conn: Int64) -> WsFrameReads the next data message (blocking). Automatically answers Ping with Pong and acknowledges Close with Close (returns the close frame so the caller can end its loop).
Ws::sendTextfn sendText(conn: Int64, s: String) -> NothingSends a text frame (opcode 1).
Ws::sendBinaryfn sendBinary(conn: Int64, bytes: List<Int64>) -> NothingSends a binary frame (opcode 2), one byte per list slot.
Ws::sendPingfn sendPing(conn: Int64) -> NothingSends a ping frame.
Ws::sendClosefn sendClose(conn: Int64, code: Int64) -> NothingSends a close frame with the given status code.
Ws::closefn close(conn: Int64) -> NothingCloses the underlying connection.
Ws::textfn text(f: WsFrame) -> StringConverts a frame's payload to a string (byte-based).
WsFrame{ fin: Bool, opcode: Int64, payload: List<Int64>, rsv1: Bool }A frame that was read. Opcode as per RFC 6455 (1 text, 2 binary, 8 close, 9 ping, 10 pong); additionally -1 = connection closed/EOF, -2 = protocol error (the connection has already been answered with a close). rsv1 is only relevant internally (permessage-deflate reassembly) — payload is already decompressed by the time readMessage returns it.

Example

import tinox.core.websocket;

let srv: Int64 = WsServer::listen(8790);

while true {
    let conn: Int64 = WsServer::accept(srv);
    if conn <= 0 { continue; }

    while true {
        let f: WsFrame = Ws::readMessage(conn);
        if f.opcode == 1 {
            Ws::sendText(conn, "echo: " + Ws::text(f));
            continue;
        }
        if f.opcode == 2 {
            Ws::sendBinary(conn, f.payload);
            continue;
        }
        break; // Close (8), EOF (-1), or protocol error (-2)
    }
    Ws::close(conn);
}

Deliberate v1 gaps: payload cap 16 MB (also applies to a reassembled fragmented message, see below, and to permessage-deflate's decompressed output, see below). wss:// (TLS) is supported, see listenTls/acceptTls above. Fragmented messages (continuation frames, §5.4) are transparently reassembled by readMessage; outgoing connections as a client are covered by WsClient, see below — WsClient deliberately doesn't offer permessage-deflate in v1 (see the permessage-deflate section below).

permessage-deflate (RFC 7692)

If a client offers Sec-WebSocket-Extensions: permessage-deflate in its handshake request, Ws::handshake (and therefore WsServer::accept/acceptTls) always accepts it with client_no_context_takeover; server_no_context_takeover (RFC 7692 §7.1.1 lets the server include these parameters unilaterally in its response) — every message gets a fresh DEFLATE context this way, so there's no persistent per-connection compression state to manage or free on close. Fully transparent to callers: readMessage already returns decompressed payload, sendText/sendBinary compress automatically once the connection has negotiated the extension — no API change, no separate *Compressed counterpart. If the client doesn't offer the extension, the connection behaves exactly as before this feature (no compression).

WsClient (outgoing connections) deliberately does not offer the extension in v1 — a real third-party server could respond with context takeover (RFC 7692's default), which would require persistent compression state across multiple messages; that's the same architectural tension that originally kept permessage-deflate out of this module, just from the client instead of the server side.

wss:// (TLS)

listenTls/acceptTls replace listen/accept 1:1 — the rest of the code (handshake, readMessage, sendText, etc.) stays unchanged, because the underlying conn-handle primitives are already transparent for TLS and plaintext connections:

import tinox.core.websocket;

let srv = WsServer::listenTls(8791, "cert.pem", "key.pem");
if srv < 0 { return 1; } // port in use, built with TINOX_TLS=0, or invalid cert/key

let conn = WsServer::acceptTls(srv); // TLS + WS handshake

OpenSSL is linked by default, just like with HttpServer::listenTls — no extra flag needed. Opt out via TINOX_TLS=0 if no OpenSSL is available for building.

Annotation-driven variant

Alternatively, the compiler generates the entire loop itself — see Annotations (@WebsocketEndpoint/@OnOpen/ @OnMessage/@OnClose). Only applies if the file defines no main of its own and contains exactly one @WebsocketEndpoint class (more than one is a compile error, not silently ignored):

import tinox.core.websocket;

@WebsocketEndpoint("/echo", 8793)
class EchoEndpoint
{
    @OnMessage
    fn onMessage(conn: Int64, msg: String) -> Nothing
    {
        Ws::sendText(conn, "echo: " + msg);
    }
}

WsClient (outgoing connections)

WsClient connects outward to a WS server, the reverse of WsServer accepting incoming connections. Role reversal versus the server API (RFC 6455 §5.1): outgoing frames are masked, incoming ones from the server must be unmasked — both directions share the same frame codec as Ws/WsServer, no separate implementation.

Class / MethodSignatureDescription
WsClient::connectfn connect(host: String, port: Int64, path: String) -> Int64Opens a TCP connection and performs the client-side handshake (its own Sec-WebSocket-Key, verifies the server's response including the Accept header). Returns the conn handle, or -1 (the connection is already closed in that case).
WsClient::readMessagefn readMessage(conn: Int64) -> WsFrameLike Ws::readMessage (including automatic Ping→Pong, close acknowledgement, fragment reassembly), just with the masking direction reversed.
WsClient::readFramefn readFrame(conn: Int64) -> WsFrameReads exactly one raw frame (no automatic control-frame handling) — like Ws::readFrame, reversed for the client role.
WsClient::sendText / sendBinary / sendPing / sendClosesame as WsSame as the identically named Ws methods, but send masked frames (required for a client).
WsClient::closefn close(conn: Int64) -> NothingCloses the underlying connection.
import tinox.core.websocket;

let conn: Int64 = WsClient::connect("example.org", 8080, "/chat");
if conn < 0 { return 1; } // connection/handshake failed

WsClient::sendText(conn, "hello");
let f: WsFrame = WsClient::readMessage(conn);
println(WsClient::text(f));

WsClient::sendClose(conn, 1000);
WsClient::close(conn);

sse

import tinox.core.sse;

Server-Sent Events (text/event-stream, the W3C EventSource protocol) — lightweight, one-way server push over plain HTTP for the common case (live dashboards, progress/notification streams) where a full WebSocket handshake and binary framing (websocket) is more than what's needed. Follows the same conn-handle-hijack shape as WsServer/Ws: SseServer::accept sends the initial 200 response with Content-Type: text/event-stream, then hands back a raw conn handle for the caller to push events over directly with Sse::sendEvent, instead of going through HttpServer's one-shot buffered request/response model (which has no incremental/chunked-write API to hold a connection open and write to it over time).

v1 scope: no Last-Event-ID/reconnection-resume handling and no retry: field (client reconnection-interval hint) — every reconnect starts a fresh stream from the caller's perspective.

Classes & Methods

Class / MethodSignatureDescription
SseServer::listen fn listen(port: Int64) -> Int64 Opens a TCP listener on the given port.
SseServer::accept fn accept(srv: Int64) -> Int64 Accepts the next connection and sends the SSE handshake response (200, Content-Type: text/event-stream, no framing). Returns the conn handle, or -1 if the request was unreadable/empty (connection already closed in that case).
SseServer::listenTls fn listenTls(port: Int64, certPath: String, keyPath: String) -> Int64 https:// variant of listen.
SseServer::acceptTls fn acceptTls(srv: Int64) -> Int64 TLS variant of accept.
Sse::sendEvent fn sendEvent(conn: Int64, event: String, data: String) -> Nothing Sends one SSE event. A multi-line data gets one data: field per line, per the EventSource spec.
Sse::sendComment fn sendComment(conn: Int64) -> Nothing Sends an SSE comment line (: keepalive) — ignored by EventSource clients, but useful to keep proxies/load balancers from timing out an otherwise-idle connection.
Sse::close fn close(conn: Int64) -> Nothing Closes the connection.

Example

import tinox.core.sse;

let srv: Int64 = SseServer::listen(8080);
let conn: Int64 = SseServer::accept(srv);
if conn > 0
{
    Sse::sendEvent(conn, "message", "hello");
    Sse::sendComment(conn); // keepalive
    Sse::close(conn);
}

amqp091

import tinox.core.amqp091;

AMQP-0-9-1 client (no broker) for message-queue brokers like RabbitMQ. Built on the same binary-safe conn-handle architecture as the WebSocket server. v1: a fixed channel per connection, an explicit publish/consume API (no lambda handler). amqps:// (TLS) is supported via connectTls. AMQP 1.0 is a separate, later module with a completely different type system.

Classes & Methods

Class / MethodSignatureDescription
AmqpConnection091::connectfn connect(host: String, port: Int64, vhost: String, user: String, pass: String) -> AmqpConnection091Connects, performs SASL PLAIN auth + negotiation. .conn <= 0 + .errorMessage on any failure (auth failure, unknown vhost, connection error).
AmqpConnection091::connectTlsfn connectTls(host: String, port: Int64, vhost: String, user: String, pass: String, verify: Bool) -> AmqpConnection091amqps:// variant of connect (broker default port 5671). OpenSSL is linked by default. verify=true checks the certificate chain + hostname against the system CA stores, verify=false is a deliberate opt-out for self-signed test certificates.
AmqpConnection091::closefn close() -> NothingCloses the connection per spec (connection.close, waits for close-ok).
AmqpChannel091::openfn open(connection: AmqpConnection091) -> AmqpChannel091Opens a new channel on the connection (sequential channel id, callable as many times as needed — multiple independent channels per connection, see below).
AmqpChannel091::declareQueuefn declareQueue(name: String, durable: Bool, exclusive: Bool, autoDelete: Bool) -> StringDeclares a queue, returns the (possibly server-generated) name, or "" on failure.
AmqpChannel091::declareExchangefn declareExchange(name: String, exchangeType: String, durable: Bool, autoDelete: Bool) -> BoolDeclares an exchange (exchangeType: "direct", "fanout", "topic", "headers" — validated broker-side). true on success, false + errorMessage on failure.
AmqpChannel091::bindQueuefn bindQueue(queue: String, exchange: String, routingKey: String) -> BoolBinds a queue to an exchange (the default exchange, one created via declareExchange(), or a broker-predefined one). Binding to the default exchange ("") is forbidden by spec (the broker responds with an error) — queues are implicitly bound there under their own name.
AmqpChannel091::qosfn qos(prefetchCount: Int64) -> BoolSets the prefetch limit before consume().
AmqpChannel091::confirmSelectfn confirmSelect() -> BoolEnables publisher confirms (RabbitMQ extension, class 85). Must be called before the first publish(), whose return value otherwise stays 0 forever.
AmqpChannel091::publishfn publish(exchange: String, routingKey: String, body: List<Int64>, contentType: String) -> Int64Publishes a message (asynchronous per spec). Return value: the assigned delivery tag (sequential starting at 1) if confirmSelect() was called beforehand, otherwise 0. The body is automatically split across multiple frames at frameMax.
AmqpChannel091::waitForConfirmfn waitForConfirm() -> AmqpConfirmResult091Blocking pull of the next publisher confirm (requires confirmSelect()). Not safe to mix with nextMessage() on a channel that's consuming at the same time (no frame dispatch by type in v1).
AmqpChannel091::consumefn consume(queue: String) -> StringRegisters the client as a consumer, returns the consumer tag, or "" on failure.
AmqpChannel091::nextMessagefn nextMessage() -> AmqpMessage091Blocking pull of the next message (requires consume()). Automatically reassembles body frames.
AmqpChannel091::ackfn ack(deliveryTag: Int64) -> NothingAcknowledges a message received via nextMessage().
AmqpMessage091{ deliveryTag: Int64, exchange: String, routingKey: String, contentType: String, body: List<Int64>, ok: Bool, errorMessage: String }A message received via nextMessage(). ok == false on any failure, errorMessage describes the cause.
AmqpConfirmResult091{ deliveryTag: Int64, multiple: Bool, ack: Bool, ok: Bool, errorMessage: String }A confirm received via waitForConfirm(). ack == false means basic.nack (the broker couldn't process the message), multiple == true confirms every tag up to and including deliveryTag at once. ok == false on any failure, errorMessage describes the cause.

Example

import tinox.core.amqp091;

let conn = AmqpConnection091::connect("127.0.0.1", 5672, "/", "guest", "guest");
let ch = AmqpChannel091::open(conn);
let queueName = ch.declareQueue("my-queue", true, false, false);

var body: List<Int64> = [];
for i in 0..3 { body.push("abc".charCodeAt(i)); }
ch.publish("", queueName, body, "text/plain");

ch.consume(queueName);
let m = ch.nextMessage();
if m.ok {
    ch.ack(m.deliveryTag);
}
conn.close();

amqps:// (TLS)

connectTls replaces connect 1:1 — the rest of the code stays unchanged, because the whole handshake runs over the same TLS-/plaintext-transparent conn primitives:

let conn = AmqpConnection091::connectTls("broker.example.com", 5671, "/", "guest", "guest", true);
// verify=true checks the certificate chain + hostname against the system CA stores;
// verify=false is a deliberate opt-out for self-signed test certificates.

OpenSSL is linked by default, just like with HttpServer::listenTls/WsServer::listenTls — no extra flag needed. Opt out via TINOX_TLS=0 if no OpenSSL is available for building.

Heartbeats (§4.2.7) run on a background thread, same explicit opt-in as amqp10: conn.heartbeat is the interval (seconds) the broker proposed in connection.tune — informational only until startHeartbeat is called.

conn.startHeartbeat(20000);   // one heartbeat frame every 20s, in the background
// ...
conn.stopHeartbeat();         // or just conn.close(), which stops it for you

Deliberate v1 gaps: no transactions (tx.select), no auto-reconnect, no dedicated CA-bundle parameter for connectTls (verifies only against the system CA stores). AMQP 1.0 is not part of this module.

Multiple channels

AmqpChannel091::open can be called any number of times on the same AmqpConnection091 — each call gets its own sequentially assigned channel id and its own frame inbox. A background task per connection reads every frame off the socket and routes it to the right inbox by channel id, so a channel with independent traffic (e.g. an active consumer) doesn't disturb another channel that's synchronously waiting for its own reply (declareQueue/qos/consume/...):

let conn = AmqpConnection091::connect("127.0.0.1", 5672, "/", "guest", "guest");
let ch1 = AmqpChannel091::open(conn);   // channel id 1
let ch2 = AmqpChannel091::open(conn);   // channel id 2, independent of ch1

Within a SINGLE channel, the v1 limitation still applies that frames aren't dispatched by method type — e.g. consuming and waiting for publisher confirms on the same channel at the same time is still unsafe (waitForConfirm could misread a basic.deliver as a confirm). Use separate channels for that case.

Publisher confirms

let selected = ch.confirmSelect();
let tag = ch.publish("amq.direct", "rk", body, "text/plain"); // tag == 1, 2, 3, ...
let confirm = ch.waitForConfirm();
if confirm.ok {
    if confirm.ack {
        // the broker accepted the message with tag confirm.deliveryTag
    } else {
        // basic.nack -- the broker couldn't process it, consider resending
    }
}

Annotation-driven consumer variant

Like WebSocket/AMQP-1.0, the compiler can alternatively generate the entire connect/open/qos/consume/receive loop itself — see Annotations (@Amqp091Consumer/ @OnMessage). Only applies if the file defines no own main and contains exactly one @Amqp091Consumer class (more than one is a compile error, never silently ignored). Prefetch is fixed at 1 in v1 (no annotation argument for it). The handler receives the entire AmqpMessage091 (not an already-decoded string) — body decoding stays with the caller, same as the manual example above:

import tinox.core.amqp091;

@Amqp091Consumer("localhost", 5672, "/", "guest", "guest", "tinox-demo-queue")
class DemoConsumer091
{
    @OnMessage
    fn onMessage(msg: AmqpMessage091) -> Nothing
    {
        // decode msg.body (List<Int64>) yourself, then e.g. log it
    }
}

amqp10

import tinox.core.amqp10;

AMQP-1.0 client (no broker), a standalone module with no code shared with amqp091 — a completely different type system (a generic format-code codec instead of fixed field tags) and a three-level ConnectionSessionLink hierarchy with credit-based flow control (flow/ link-credit) instead of 0-9-1's simple ConnectionChannel model. v1: one session/one link per purpose, SASL PLAIN only.

Classes & Methods

Class / MethodSignatureDescription
Amqp10Connection::connectfn connect(host: String, port: Int64, user: String, pass: String) -> Amqp10ConnectionSASL header + PLAIN negotiation, then AMQP header + open. .conn <= 0 + .errorMessage on any failure.
Amqp10Connection::closefn close() -> NothingCloses the connection per spec (sends close).
Amqp10Session::beginfn begin(connection: Amqp10Connection) -> Amqp10SessionOpens a session on the connection (v1: fixed channel 1, no pool).
Amqp10Session::endfn end() -> NothingEnds the session (sends end).
Amqp10Link::attachfn attach(session: Amqp10Session, name: String, role: Bool, address: String) -> Amqp10LinkAttaches a sender (role=false) or receiver link (role=true) to the given address (RabbitMQ 4.x: /queues/<name>, "AMQP Address v2").
Amqp10Link::detachfn detach() -> NothingDetaches the link again (sends detach).
Amqp10Link::awaitFlowfn awaitFlow() -> NothingReads a flow frame and updates linkCredit. Called internally by publish() when no credit remains.
Amqp10Link::grantCreditfn grantCredit(amount: Int64) -> NothingSends flow with link-credit (receiver-side, needed before nextMessage()).
Amqp10Link::publishfn publish(body: List<Int64>, contentType: String) -> NothingPublishes a message (settled=true, fire-and-forget). Automatically waits for a new flow if credit is exhausted; splits the body across multiple transfer frames (more=true) once max-frame-size is exceeded.
Amqp10Link::nextMessagefn nextMessage() -> Amqp10MessageBlocking pull of the next message (requires grantCredit()). Decodes both data AND amqp-value body sections. Does NOT reassemble multi-frame transfers on receive (v1 gap).
Amqp10Link::ackfn ack(deliveryId: Int64) -> NothingAcknowledges a message received via nextMessage() (disposition with state=accepted).
Amqp10Message{ body: List<Int64>, contentType: String, deliveryId: Int64, ok: Bool, errorMessage: String }A message received via nextMessage(). ok == false on any failure (including no recognized body section), errorMessage describes the cause.

Example

import tinox.core.amqp10;

let conn = Amqp10Connection::connect("127.0.0.1", 5672, "guest", "guest");
let session = Amqp10Session::begin(conn);
var sender = Amqp10Link::attach(session, "my-sender", false, "/queues/my-queue");

var body: List<Int64> = [];
for i in 0..3 { body.push("abc".charCodeAt(i)); }
sender.publish(body, "text/plain");
sender.detach();

var receiver = Amqp10Link::attach(session, "my-receiver", true, "/queues/my-queue");
receiver.grantCredit(10);
let m = receiver.nextMessage();
if m.ok {
    receiver.ack(m.deliveryId);
}
conn.close();

Annotation-driven variant

Like WebSocket, the compiler can alternatively generate the whole connect/attach/receive loop itself — see Annotations (@Amqp10Consumer/ @OnMessage). Only applies if the file defines no own main and contains exactly one @Amqp10Consumer class (more than one is a compile error, not silently ignored). The handler receives the whole Amqp10Message (not an already decoded String) — body decoding stays with the caller, exactly like the manual example above:

import tinox.core.amqp10;

@Amqp10Consumer("localhost", 5672, "guest", "guest", "/queues/tinox-demo-queue")
class DemoConsumer
{
    @OnMessage
    fn onMessage(msg: Amqp10Message) -> Nothing
    {
        // decode msg.body (List<Int64>) yourself, then e.g. log it
    }
}

Deliberate v1 gaps: multi-frame reassembly on receive (sending already splits correctly once max-frame-size is exceeded; receiving only detects more=true and reports an error instead of reassembling).

smtp

import tinox.core.smtp;

SMTP client (RFC 5321) for sending email — the common "send a password reset email / signup confirmation" need. Supports plaintext connect, RFC 3207 STARTTLS (upgrades an already-connected plaintext session in place, port 587), implicit TLS (port 465), and SASL AUTH PLAIN (RFC 4954/4616). connect()/ connectTls() read the greeting and send EHLO automatically; startTls() re-issues EHLO after the upgrade, as RFC 3207 requires.

v1 sends a plain-text body only — no multipart/HTML/attachments. No DKIM signing (the sending mail server's job when routing through a real relay like SES/SendGrid, not this client library's).

Classes & Methods

Class / MethodSignatureDescription
SmtpClient::connect fn connect(host: String, port: Int64) -> SmtpClient Plaintext connect, reads the greeting, sends EHLO. Check conn <= 0/errorMessage for failure.
SmtpClient::connectTls fn connectTls(host: String, port: Int64, verify: Bool) -> SmtpClient Implicit-TLS variant of connect (port 465).
SmtpClient::startTls fn startTls() -> Bool RFC 3207: upgrades the connection to TLS in place (port 587 flow), then re-sends EHLO. Certificate verification is always on.
SmtpClient::authPlain fn authPlain(user: String, pass: String) -> Bool SASL AUTH PLAIN.
SmtpClient::send fn send(from: String, to: List<String>, subject: String, body: String) -> Bool Sends a plain-text email (MAIL FROM/RCPT TO/DATA, with RFC 5321 §4.5.2 dot-stuffing handled automatically).
SmtpClient::close fn close() -> Nothing Closes the connection.

Example

import tinox.core.smtp;

let client: SmtpClient = SmtpClient::connect("smtp.example.org", 587);
if client.conn <= 0 { throw client.errorMessage; }
if !client.startTls() { throw "STARTTLS failed"; }
if !client.authPlain("user", "pass") { throw "auth failed"; }
client.send("me@example.org", ["you@example.org"], "Hi", "Hello!");
client.close();

redis

import tinox.core.redis;

Client for an external cache/session store (RESP2, single-node, request/response commands) — tinox.core.cache's in-process Cache/LruCache can't share state across HTTP worker processes/instances or store sessions outside a single process's memory. Wire-compatible with both Redis and Valkey.

v1 scope: no connection pooling (tinox.core.pool is the generic building block for this, not reinvented here), no Redis Cluster/Sentinel, no pub/sub (SUBSCRIBE/PUBLISH) — single-node, request/response commands only. No Memcached client in this pass (different wire protocol, an explicit separately-scoped follow-up if needed).

Classes & Methods

Class / MethodSignatureDescription
RedisClient::connect fn connect(host: String, port: Int64) -> RedisClient Dials and sends PING to fail fast on a bad host/port. Check conn <= 0/errorMessage for failure.
RedisClient::get fn get(key: String) -> String Returns "" both on a real cache miss and on a protocol error — lastGetFound() distinguishes the two, since an empty string is itself a valid stored value.
RedisClient::lastGetFound fn lastGetFound() -> Bool Whether the most recent get() found a value. Check immediately after calling get().
RedisClient::set fn set(key: String, value: String) -> Bool Sets a key unconditionally.
RedisClient::setEx fn setEx(key: String, value: String, ttlSeconds: Int64) -> Bool Sets a key with an expiration.
RedisClient::del fn del(key: String) -> Bool Deletes a key; true if it existed.
RedisClient::expire fn expire(key: String, ttlSeconds: Int64) -> Bool Sets a TTL on an existing key.
RedisClient::incr fn incr(key: String) -> Int64 Atomically increments a key and returns the new value. lastIncrOk() distinguishes a real result of 0 from a protocol/value error.
RedisClient::lastIncrOk fn lastIncrOk() -> Bool Whether the most recent incr() succeeded.
RedisClient::close fn close() -> Nothing Closes the connection.

Example

import tinox.core.redis;

let client: RedisClient = RedisClient::connect("localhost", 6379);
if client.conn <= 0 { throw client.errorMessage; }
client.setEx("session:abc", "user-42", 3600);
let value: String = client.get("session:abc");
if !client.lastGetFound() { throw "cache miss"; }
client.close();

hpack

import tinox.core.hpack;

Implementation of HPACK header compression for HTTP/2 according to RFC 7541. The module provides an encoder and decoder that work with a static table (61 entries) and a configurable dynamic table per connection. Supports all four representation types from RFC 7541 §6: indexed headers, literals with/without incremental indexing, and dynamic table size updates.

Classes & Methods

Class / MethodSignatureDescription
HpackHeader::newfn new(name: String, value: String) -> HpackHeaderCreates a header name/value pair.
HpackDynTable::newfn new(maxSize: Int64) -> HpackDynTableCreates a new dynamic HPACK table with the specified maximum size (in bytes).
HpackDynTable::addfn add(header: HpackHeader) -> NothingInserts a header at the front and evicts older entries from the back if necessary.
HpackDynTable::setMaxSizefn setMaxSize(newSize: Int64) -> NothingChanges the maximum size of the table and evicts entries if necessary.
Hpack::decodefn decode(data: List<Int64>, dynTable: HpackDynTable) -> List<HpackHeader>Decodes a complete HPACK header block into an ordered list of headers.
Hpack::encodefn encode(headers: List<HpackHeader>, dynTable: HpackDynTable) -> List<Int64>Encodes a header list into an HPACK header block. Uses indexed representation for full matches in the static table, otherwise literal with incremental indexing.
Hpack::decodeIntfn decodeInt(data: List<Int64>, offset: Int64, prefixBits: Int64) -> HpackIntResultDecodes an HPACK integer with the specified prefix width (RFC 7541 §5.1).
Hpack::decodeStrfn decodeStr(data: List<Int64>, offset: Int64) -> HpackStrResultDecodes an HPACK string literal (RFC 7541 §5.2). Huffman encoding is passed through without error, but not decoded.
Hpack::encodeIntfn encodeInt(value: Int64, prefixBits: Int64, firstByteMask: Int64) -> List<Int64>Encodes an integer with the given prefix width (RFC 7541 §5.1).
Hpack::encodeStrfn encodeStr(s: String) -> List<Int64>Encodes a string without Huffman encoding (RFC 7541 §5.2).
Hpack::lookupHeaderfn lookupHeader(dynTable: HpackDynTable, index: Int64) -> HpackHeaderLooks up a header by the combined static+dynamic index (1-based).
Hpack::findStaticfn findStatic(name: String, value: String) -> Int64Searches for a full or name-based match in the static table; returns -1 if not found.
Hpack::staticNamefn staticName(index: Int64) -> StringReturns the header name for a static table index (indices 1–61).
Hpack::staticValuefn staticValue(index: Int64) -> StringReturns the header value for a static table index (only entries with a value).

Example

import tinox.core.hpack;

// Dynamic tables for encoder and decoder (per connection)
let encTable: HpackDynTable = HpackDynTable::new(4096);
let decTable: HpackDynTable = HpackDynTable::new(4096);

// Header-Liste kodieren
let headers: List<HpackHeader> = [];
headers.push(HpackHeader::new(":status", "200"));
headers.push(HpackHeader::new("content-type", "application/json"));
headers.push(HpackHeader::new("x-request-id", "abc-123"));

let encoded: List<Int64> = Hpack::encode(headers, encTable);

// ... send encoded over the network ...

// Decode on receiver side
let decoded: List<HpackHeader> = Hpack::decode(encoded, decTable);

let i: Int64 = 0;
while i < decoded.len() {
    print(decoded[i].name + ": " + decoded[i].value);
    i = i + 1;
}

// Adjust table size to peer specification
encTable.setMaxSize(2048);

http

import tinox.core.http;

Simple HTTP client for direct network requests. The class Http provides static methods for all common HTTP verbs (GET, POST, PUT, PATCH, DELETE) and the ability to set and clear global default headers. Responses are returned as HttpClientResponse, which provides the status code, body, and individual header values. For higher-level abstraction, use the rest module.

https:// is supported (issue #131) — detected automatically from the URL scheme, no separate method needed. The certificate chain and hostname are always verified against the system CA stores (no opt-out like connectTls/ listenTls in other modules — this client is intentionally v1-scoped to real, publicly-trusted endpoints only, e.g. OAuth2 token endpoints, see the oauth2 module). When built with TINOX_TLS=0, an https:// call returns the same empty HttpClientResponse (statusCode() == 0) as any other connection failure.

Classes & Methods

Class / MethodSignatureDescription
Http::getfn get(url: String) -> HttpClientResponseExecutes an HTTP GET request to the specified URL.
Http::postfn post(url: String, body: String) -> HttpClientResponseExecutes an HTTP POST request with the specified body.
Http::putfn put(url: String, body: String) -> HttpClientResponseExecutes an HTTP PUT request with the specified body.
Http::deletefn delete(url: String) -> HttpClientResponseExecutes an HTTP DELETE request.
Http::patchfn patch(url: String, body: String) -> HttpClientResponseExecutes an HTTP PATCH request with the specified body.
Http::setHeaderfn setHeader(name: String, value: String) -> NothingSets a global default header for subsequent requests.
Http::clearHeadersfn clearHeaders() -> NothingClears all previously set global headers.
HttpClientResponse::statusCodefn statusCode() -> Int64Returns the HTTP status code of the response.
HttpClientResponse::bodyfn body() -> StringReturns the response body as a string.
HttpClientResponse::headerfn header(name: String) -> StringReturns the value of a response header.

Example

import tinox.core.http;

// Simple GET request
let resp: HttpClientResponse = Http::get("https://api.example.com/status");
print("Status: " + resp.statusCode().toString());
print("Body:   " + resp.body());

// POST with JSON body and Authorization header
Http::setHeader("Authorization", "Bearer my-token");
Http::setHeader("Content-Type", "application/json");

let post: HttpClientResponse = Http::post(
    "https://api.example.com/users",
    "{\"name\": \"Max\", \"email\": \"max@example.com\"}"
);

if post.statusCode() == 201 {
    print("User created: " + post.body());
} else {
    print("Error: " + post.statusCode().toString());
}

Http::clearHeaders();

// PATCH-Request
let patch: HttpClientResponse = Http::patch(
    "https://api.example.com/users/42",
    "{\"name\": \"Maxine\"}"
);
print("Content-Type: " + patch.header("Content-Type"));

rest.client

import tinox.core.rest.client;

RESTful HTTP client with convenient abstraction over the low-level http module. Provides RestClient for requests with default headers and authentication, and RequestBuilder for fluent (method-chaining) request construction. Responses are wrapped in RestResponse, which offers convenient helpers for status checks and JSON parsing. Additionally, the module contains URL helper functions and enums for HTTP status codes and MIME types.

Classes & Methods

Class / MethodSignatureDescription
RestClient::newfn new(baseUrl: String) -> RestClientCreates a new REST client with the specified base URL (timeout 30 s).
RestClient::newWithHeadersfn newWithHeaders(baseUrl: String, defaultHeaders: List<String>) -> RestClientCreates a client with pre-configured default headers.
RestClient::setTimeoutfn setTimeout(timeoutMs: Int64) -> NothingSets the request timeout in milliseconds.
RestClient::addHeaderfn addHeader(name: String, value: String) -> NothingAdds a default header for all requests.
RestClient::clearHeadersfn clearHeaders() -> NothingClears all default headers.
RestClient::getfn get(path: String) -> RestResponseExecutes a GET request to baseUrl + path.
RestClient::getWithParamsfn getWithParams(path: String, params: Map<String, String>) -> RestResponseGET request with query parameters (URL-encoded).
RestClient::postfn post(path: String, body: String) -> RestResponsePOST request with JSON body (sets Content-Type automatically).
RestClient::putfn put(path: String, body: String) -> RestResponsePUT request with JSON body.
RestClient::patchfn patch(path: String, body: String) -> RestResponsePATCH request with JSON body.
RestClient::deletefn delete(path: String) -> RestResponseDELETE request.
RestClient::bearerAuthfn bearerAuth(token: String) -> NothingAdds a Bearer token Authorization header.
RestClient::basicAuthfn basicAuth(username: String, password: String) -> NothingAdds a Basic Auth header (Base64-encoded).
RestResponse::newfn new(statusCode: Int64, body: String) -> RestResponseCreates a REST response instance.
RestResponse::textfn text() -> StringReturns the response body as a string.
RestResponse::jsonfn json() -> JsonValueParses the body as JSON.
RestResponse::isOkfn isOk() -> BoolReturns true if the status code is in the range 200–299.
RestResponse::isClientErrorfn isClientError() -> BoolReturns true for status codes 400–499.
RestResponse::isServerErrorfn isServerError() -> BoolReturns true for status codes 500+.
RestResponse::statusfn status() -> Int64Returns the numeric status code.
RestResponse::ensureSuccessfn ensureSuccess() -> RestResponseThrows an error if the response was not successful (not 2xx).
Url::buildfn build(baseUrl: String, path: String) -> StringJoins base URL and path into a complete URL.
Url::buildWithParamsfn buildWithParams(baseUrl: String, path: String, params: Map<String, String>) -> StringBuilds a URL with URL-encoded query parameters.
Url::encodefn encode(s: String) -> StringPercent-encodes a string for use in URLs.
RequestBuilder::newfn new(baseUrl: String) -> RequestBuilderCreates a fluent request builder.
RequestBuilder::headerfn header(name: String, value: String) -> RequestBuilderAdds a header to the request (chaining).
RequestBuilder::queryfn query(key: String, value: String) -> RequestBuilderAdds a query parameter (chaining).
RequestBuilder::jsonBodyfn jsonBody(jsonBody: String) -> RequestBuilderSets a JSON body and adds the Content-Type header (chaining).
RequestBuilder::bearerfn bearer(token: String) -> RequestBuilderAdds a Bearer token header (chaining).
RequestBuilder::getfn get(path: String) -> RestResponseExecutes the configured GET request.
RequestBuilder::postfn post(path: String) -> RestResponseExecutes the configured POST request.

Example

import tinox.core.rest.client;

// RestClient mit Bearer-Auth
let client: RestClient = RestClient::new("https://api.example.com");
client.bearerAuth("mein-jwt-token");

// GET mit Query-Parametern
let params: Map<String, String> = Map::new();
params["page"] = "1";
params["limit"] = "20";
let list: RestResponse = client.getWithParams("/users", params);
list.ensureSuccess();
print(list.text());

// POST – create new user
let create: RestResponse = client.post("/users", "{\"name\": \"Anna\"}");
if create.isOk() {
    let data: JsonValue = create.json();
    print("Created: " + data.toString());
}

// Fluenter RequestBuilder
let resp: RestResponse = RequestBuilder::new("https://api.example.com")
    .bearer("my-jwt-token")
    .query("format", "compact")
    .jsonBody("{\"active\": true}")
    .post("/users/42/settings");

if resp.isClientError() {
    print("Clientfehler: " + resp.status().toString());
}

rest.server

import tinox.core.rest.server;

Annotation-driven REST framework for building HTTP APIs with declarative routing. Controller classes are declared with annotations such as @Path, @GET, @POST, @Produces, @Consumes, @StatusCode, @Auth, and @OIDCRolesAllowed, then registered with RestApi (or, for purely annotation-based controllers with no explicit RestApi wiring, translated by the compiler directly into route-dispatch code, including an auto-generated main() if none exists). The framework supports global middleware, CORS, automatic content-type management, and dependency injection annotations (@ApplicationComponent, @Inject, @Startup, @HttpRequestScoped).

@Auth("bearer")/@Auth("basic") now actually validates the credential (since issue #141) instead of only checking the Authorization header's scheme prefix (previously any syntactically matching "Bearer ..."/ "Basic ..." with an arbitrary value was accepted). For controllers registered via the RestApi class, this was already possible via RestApi::setAuthValidator; for purely annotation-based controllers (compiled routes with no RestApi instance), the compiler now looks for a project- defined class AuthValidator with fnc validate(authType: String, credential: String) -> Bool and calls it if present — without such a class, every @Auth-protected request is rejected (safe default instead of silently open, matching RestApi::authValidator's own default behavior).

@OIDCRolesAllowed(["role1", "role2"]) (issue #142) requires a verified OIDC access token carrying at least one of the listed realm roles — entirely declarative, with the handler itself never aware any of this happened. Verification (RS256 signature via the IdP's JWKS, iss/aud/exp/ nbf, role matching against realm_access.roles) runs automatically via OidcGuard::checkRoles; the IdP connection itself (OIDC_ISSUER/OIDC_JWKS_URI/ OIDC_AUDIENCE) comes from environment variables, so the same compiled binary works against any RS256/JWKS-capable IdP without a rebuild — see examples/keycloak_oidc_api for a full example with a real Keycloak via docker-compose. Known issue (issue #140): the compiler's auto-generated, epoll-/thread-pool-based server for annotation-driven routes (tinox_HttpServer_listen in runtime.c — a different, multi-threaded server from the single-threaded HttpServer class documented below) can crash inside the Boehm GC after a handful of requests to any allocation- heavy route (not specific to @OIDCRolesAllowed — reproduces with a trivial route with no crypto/JSON/network involvement). Light, interactive use works reliably; the HttpServer class itself is unaffected.

Classes & Methods

Class / MethodSignatureDescription
OIDCRolesAllowed@OIDCRolesAllowed(roles: List<String>)Method annotation (issue #142): requires a verified OIDC access token carrying at least one of the listed realm roles. Needs OIDC_ISSUER/OIDC_JWKS_URI/OIDC_AUDIENCE as runtime environment variables.
OidcGuard::checkRolesfnc checkRoles(ctx: HttpContext, rolesCsv: String) -> BoolBacks @OIDCRolesAllowed — called by the compiler under a well-known symbol name; not meant for direct use from application code. Sets ctx.response itself on failure (401 missing/invalid token, 403 valid token missing the required role).
RestApi::newfn new(port: Int64) -> RestApiCreates a new REST API application on the specified port.
RestApi::registerfn register(controller: RestController) -> RestApiRegisters all routes of a controller (chaining).
RestApi::getfn get(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApiRegisters a GET handler directly (fluent API).
RestApi::postfn post(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApiRegisters a POST handler directly.
RestApi::putfn put(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApiRegisters a PUT handler directly.
RestApi::patchfn patch(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApiRegisters a PATCH handler directly.
RestApi::deletefn delete(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApiRegisters a DELETE handler directly.
RestApi::usefn use(mware: fnc(HttpContext) -> Bool) -> RestApiAdds a global middleware.
RestApi::enableCorsfn enableCors(origin: String) -> RestApiEnables CORS for all responses with the specified allowed origin.
RestApi::defaultJsonfn defaultJson() -> RestApiSets application/json as the default content type if not already set.
RestApi::startfn start() -> NothingRegisters all routes and starts the HTTP server (blocking).
RestApi::stopfn stop() -> NothingStops the server.
RestApi::wrapHandlerfn wrapHandler(entry: RouteEntry) -> fnc(HttpContext) -> NothingWraps a handler with annotation metadata: status code, content type, auth check.
RestController::newfn new() -> RestControllerCreates a new controller (base class for annotated controllers).
RestController::routefn route(method: String, path: String, handler: fnc(HttpContext) -> Nothing) -> RouteEntryRegisters a route manually on the controller.
RouteEntry::newfn new(method: String, path: String, handler: fnc(HttpContext) -> Nothing) -> RouteEntryCreates a new route entry.
RouteEntry::withStatusfn withStatus(code: Int64) -> RouteEntrySets the default status code for the route (chaining).
RouteEntry::withProducesfn withProduces(mediaType: MediaType) -> RouteEntrySets the response content type of the route (chaining).
RouteEntry::withConsumesfn withConsumes(mediaType: MediaType) -> RouteEntrySets the expected request content type of the route (chaining).
RouteEntry::withAuthfn withAuth(authType: String) -> RouteEntrySets the authentication type of the route ("bearer" or "basic") (chaining).
UrlBuilder::joinfn join(base: String, path: String) -> StringJoins base and route path without double slashes.

Example

import tinox.core.rest.server;

@Path("/api/users")
class UserController : RestController
{
    fn new() -> UserController
    {
        let ctrl: UserController = UserController { routes: [], basePath: "/api/users" };
        ctrl.route("GET", "/", ctx => {
            ctx.response.json("[{\"id\": 1, \"name\": \"Alice\"}]");
        }).withProduces(MediaType::Json);

        ctrl.route("POST", "/", ctx => {
            let body: JsonValue = ctx.request.json();
            ctx.response.status(201).json("{\"created\": true}");
        }).withStatus(201).withConsumes(MediaType::Json).withProduces(MediaType::Json);

        ctrl.route("GET", "/:id", ctx => {
            let id: String = ctx.request.getParam("id");
            ctx.response.json("{\"id\": " + id + "}");
        }).withProduces(MediaType::Json).withAuth("bearer");

        return ctrl;
    }
}

let app: RestApi = RestApi::new(8080);
app.enableCors("*");
app.defaultJson();
app.use(ctx => {
    print("Request: " + ctx.request.method + " " + ctx.request.path);
    return true;
});
app.register(UserController::new());
app.start();

socket

import tinox.core.socket;

Simple socket abstraction for TCP and UDP network communication. The Socket class encapsulates native system sockets and provides methods for connecting, binding, listening, accepting connections, sending and receiving data, and closing the socket. TCP sockets are typically used for connection-oriented protocols, UDP sockets for connectionless communication.

Classes & Methods

Class / MethodSignatureDescription
Socket::createTcpfn createTcp() -> SocketCreates a new TCP socket.
Socket::createUdpfn createUdp() -> SocketCreates a new UDP socket.
Socket::connectfn connect(socket: Socket, host: String, port: Int64) -> BoolConnects the socket to the specified host and port. Returns true on success.
Socket::bindfn bind(socket: Socket, port: Int64) -> BoolBinds the socket to a local port.
Socket::listenfn listen(socket: Socket) -> BoolPuts the socket into listen mode (TCP server only).
Socket::acceptfn accept(socket: Socket) -> SocketAccepts an incoming connection and returns a new socket.
Socket::sendfn send(socket: Socket, data: String) -> Int64Sends data over the socket. Returns the number of bytes sent.
Socket::receivefn receive(socket: Socket, size: Int64) -> StringReceives up to size bytes from the socket and returns them as a string.
Socket::closefn close(socket: Socket) -> NothingCloses the socket and frees system resources.

Example

import tinox.core.socket;

// === TCP-Server ===
let server: Socket = Socket::createTcp();
let bound: Bool = Socket::bind(server, 9000);
if bound == false {
    print("Bind fehlgeschlagen");
}

let listening: Bool = Socket::listen(server);
print("Server listening on port 9000");

// Accept incoming connection
let client: Socket = Socket::accept(server);

// Receive data
let data: String = Socket::receive(client, 1024);
print("Received: " + data);

// Send response
Socket::send(client, "HTTP/1.1 200 OK\r\n\r\nHello!");
Socket::close(client);
Socket::close(server);

// === TCP-Client ===
let conn: Socket = Socket::createTcp();
let ok: Bool = Socket::connect(conn, "api.example.com", 80);
if ok {
    Socket::send(conn, "GET / HTTP/1.1\r\nHost: api.beispiel.de\r\n\r\n");
    let resp: String = Socket::receive(conn, 4096);
    print(resp);
    Socket::close(conn);
}

jwt

import tinox.core.jwt;

JSON Web Token (JWT) implementation with HMAC-SHA256 (HS256) and, since issue #138, RSASSA-PKCS1-v1_5-SHA256 (RS256, against a provider key fetched via Jwks) signing. The module allows creating signed JWTs from a payload map, verifying the signature, decoding the header/payload, and checking the expiry time based on the exp claim. All three JWS segments are base64url-encoded (RFC 7515 §3) — before issue #137 the signature segment was incorrectly raw hex instead of base64url, so no external JWT client could verify a Tinox-issued token (or vice versa); since then the module interoperates with standard libraries (verified against PyJWT, among others).

Classes & Methods

Class / MethodSignatureDescription
Jwt::encodefn encode(payload: Map<String, JsonValue>, secret: String) -> StringCreates an HS256-signed JWT string from the payload map and the secret key.
Jwt::decodefn decode(token: String, secret: String) -> Map<String, JsonValue>Verifies the HS256 signature (header alg must be exactly "HS256") and returns the decoded payload. Throws on invalid format, wrong alg, or wrong signature.
Jwt::verifyfn verify(token: String, secret: String) -> BoolChecks whether the HS256 signature is valid without returning the payload.
Jwt::decodeRs256fn decodeRs256(token: String, n: String, e: String) -> Map<String, JsonValue>RS256 counterpart of decoden/e are an RSA public key from a JWK (base64url, e.g. via Jwks::fetchRsaKey). Checks header alg == "RS256" (alg-confusion hardening).
Jwt::verifyRs256fn verifyRs256(token: String, n: String, e: String) -> BoolRS256 counterpart of verify.
Jwt::extractHeaderfn extractHeader(token: String) -> Map<String, JsonValue>Decodes the JWT header (for alg/kid) without signature verification — needed to know which key/algorithm to verify with before verification happens (see OidcClient, module oidc).
Jwt::extractPayloadfn extractPayload(token: String) -> Map<String, JsonValue>Decodes the payload of a token without signature verification (only for trusted sources).
Jwt::isExpiredfn isExpired(token: String) -> BoolReturns true if the exp claim is in the past. Tokens without exp are considered not expired.
Jwks::fetchRsaKeyfn fetchRsaKey(jwksUri: String, kid: String) -> Map<String, String>Fetches a JWKS document (RFC 7517 §5) over HTTPS and returns {"n", "e"} of the RSA key matching kid. kid == "" is only unambiguous if the JWKS contains exactly one RSA key — otherwise this throws rather than silently using the first match.

Example

import tinox.core.jwt;

let secret: String = "my-secret-key";

// Create token
let payload: Map<String, JsonValue> = Map::new();
payload["sub"] = JsonValue::fromString("user-42");
payload["name"] = JsonValue::fromString("Anna Sample");
payload["exp"] = JsonValue::fromInt(Time::now() / 1000 + 3600);  // 1 hour

let token: String = Jwt::encode(payload, secret);
print("Token: " + token);

// Verify signature
let valid: Bool = Jwt::verify(token, secret);
print("Valid: " + valid.toString());

// Decode token and read payload
let decoded: Map<String, JsonValue> = Jwt::decode(token, secret);
print("User: " + decoded["name"].asString());

// Check expiry
let expired: Bool = Jwt::isExpired(token);
print("Expired: " + expired.toString());

// Middleware example: validate Bearer token
let checkJwt: fnc(HttpContext) -> Bool = ctx => {
    let auth: String = ctx.request.getHeader("Authorization");
    if auth.startsWith("Bearer ") == false {
        ctx.response.status(401).text("No token");
        return false;
    }
    let tok: String = auth.substring(7, auth.len());
    if Jwt::verify(tok, secret) == false || Jwt::isExpired(tok) {
        ctx.response.status(401).text("Token invalid or expired");
        return false;
    }
    return true;
};

oauth2

import tinox.core.oauth2;

OAuth2 client for the Authorization Code grant with PKCE (RFC 6749 + RFC 7636) — the recommended flow for both server and public clients against third-party providers (Google, GitHub, …). OAuth2Client encapsulates the full flow: building the authorize URL (including a secure state for CSRF protection and a PKCE code_challenge), exchanging the callback code for a token pair, and refreshing tokens. The underlying http client gained https:// support alongside this module (strict certificate verification), so real provider endpoints can be reached directly.

Intentional v1 gaps: only Authorization Code + PKCE (no client_credentials/implicit/ device_code), no token storage — as with the rest of the rest/http_server framework, that remains the caller's responsibility. For "login with X" including a verified OIDC ID token (signature, iss/aud), see the oidc module (OidcClient, issue #138), which builds on this client.

Classes & Methods

Class / MethodSignatureDescription
OAuth2Client::new fn new(authorizeUrl: String, tokenUrl: String, clientId: String, clientSecret: String, redirectUri: String) -> OAuth2Client Creates a client for a concrete provider (authorize/token endpoint) and this application (client ID/secret, registered redirect URI).
OAuth2Client::buildAuthorizeUrl fn buildAuthorizeUrl(scope: String) -> OAuth2AuthorizeRequest Builds the URL the user's browser is redirected to. Generates a cryptographically secure state (CSRF) and a PKCE code_verifier/code_challenge (S256) along the way. The state and codeVerifier from the result MUST be stored session-side.
OAuth2Client::exchangeCode fn exchangeCode(code: String, codeVerifier: String) -> OAuth2TokenResponse Exchanges the code delivered by the provider in the callback for an access/refresh token pair (RFC 6749 §4.1.3, form-urlencoded POST). codeVerifier must be the previously stored value from buildAuthorizeUrl.
OAuth2Client::refresh fn refresh(refreshToken: String) -> OAuth2TokenResponse Exchanges a refresh token for a fresh token pair (RFC 6749 §6).
OAuth2AuthorizeRequest { url: String, state: String, codeVerifier: String } Result of buildAuthorizeUrl. state/codeVerifier must be kept until the callback.
OAuth2TokenResponse { accessToken, refreshToken, tokenType, idToken: String, expiresIn: Int64, ok: Bool, errorMessage: String } ok == false on any failure (network/TLS, HTTP error status, or an RFC 6749 §5.2 error response from the provider) — errorMessage describes the cause instead of silently passing through an empty/incorrect token. idToken is the token endpoint's id_token field, if present ("" otherwise) — unverified, see the oidc module for verified OIDC claims.

Example

import tinox.core.oauth2;

class Main
{
    fnc main() -> Int32
    {
        let client: OAuth2Client = OAuth2Client::new(
            "https://accounts.google.com/o/oauth2/v2/auth",
            "https://oauth2.googleapis.com/token",
            "my-client-id",
            "my-client-secret",
            "https://my-app.example.com/callback"
        );

        // 1. Redirect the user to the provider's login page
        let authReq: OAuth2AuthorizeRequest = client.buildAuthorizeUrl("openid email");
        // Store authReq.state and authReq.codeVerifier session-side,
        // then redirect the browser to authReq.url.
        println("Redirecting to: " + authReq.url);

        // 2. In the callback handler: verify state against the stored
        //    value, then exchange the code for a token pair.
        let tokens: OAuth2TokenResponse = client.exchangeCode("code-from-callback", authReq.codeVerifier);
        if tokens.ok {
            println("Access token: " + tokens.accessToken);
        } else {
            println("Error: " + tokens.errorMessage);
        }

        // 3. Later: refresh the token
        let refreshed: OAuth2TokenResponse = client.refresh(tokens.refreshToken);
        if refreshed.ok {
            println("New access token: " + refreshed.accessToken);
        }
        return 0;
    }
}
  

oidc

import tinox.core.oidc;

"Login with X" (OpenID Connect) on top of OAuth2Client (module oauth2): OidcClient runs the same Authorization Code + PKCE flow, but also verifies the provider's id_token — RS256 signature against the provider key fetched via Jwks (module jwt), plus iss/aud/exp/nbf — instead of trusting it unverified, and returns the verified claims (sub, email, …) (issue #138).

Intentional v1 gaps: no JWKS caching (every verification fetches the JWKS document fresh — a caching layer is separate follow-up work if this becomes a real cost), only Authorization Code + PKCE (no client_credentials/ implicit/device_code), no token storage — same stance as oauth2.

Classes & Methods

Class / MethodSignatureDescription
OidcClient::new fn new(issuer: String, authorizeUrl: String, tokenUrl: String, jwksUri: String, clientId: String, clientSecret: String, redirectUri: String) -> OidcClient Like OAuth2Client::new, plus issuer (expected iss claim) and jwksUri (provider JWKS endpoint).
OidcClient::buildAuthorizeUrl fn buildAuthorizeUrl(scope: String) -> OAuth2AuthorizeRequest Like OAuth2Client::buildAuthorizeUrl, but first checks that scope includes "openid" — otherwise the provider won't return an id_token at all, which would otherwise only surface as a confusing error at exchangeCode. Throws immediately if "openid" is missing.
OidcClient::exchangeCode fn exchangeCode(code: String, codeVerifier: String) -> OidcTokenResponse Exchanges the code for a token pair (like OAuth2Client::exchangeCode) and verifies the id_token. If id_token is missing from the response, that is an error here — unlike refresh.
OidcClient::refresh fn refresh(refreshToken: String) -> OidcTokenResponse Refreshes the token pair. If the provider doesn't return a new id_token (optional per spec for this grant), that is not an error — claims is then empty.
OidcTokenResponse { accessToken, refreshToken, tokenType, idToken: String, expiresIn: Int64, claims: Map<String, JsonValue>, ok: Bool, errorMessage: String } ok == false on any OAuth2 failure (see OAuth2TokenResponse) AND on any ID-token verification failure (wrong alg, invalid signature, mismatched iss/aud, expired) — claims is only populated when ok == true and an id_token was present.

Example

import tinox.core.oidc;

class Main
{
    fnc main() -> Int32
    {
        let client: OidcClient = OidcClient::new(
            "https://accounts.google.com",
            "https://accounts.google.com/o/oauth2/v2/auth",
            "https://oauth2.googleapis.com/token",
            "https://www.googleapis.com/oauth2/v3/certs",
            "my-client-id",
            "my-client-secret",
            "https://my-app.example.com/callback"
        );

        // 1. Redirect the user to the provider's login page ("openid" is required)
        let authReq: OAuth2AuthorizeRequest = client.buildAuthorizeUrl("openid email");
        println("Redirecting to: " + authReq.url);

        // 2. In the callback handler: verify state, then exchange the code + verify id_token
        let tokens: OidcTokenResponse = client.exchangeCode("code-from-callback", authReq.codeVerifier);
        if tokens.ok {
            println("Logged in as: " + tokens.claims["sub"].getString());
        } else {
            println("Error: " + tokens.errorMessage);
        }
        return 0;
    }
}
  

OidcWebApp — browser login "out of the box"

OidcClient above is the low-level building block — the caller has to store state/codeVerifier itself and manage a session. OidcWebApp takes care of exactly that for the classic "visit a page, get bounced to Keycloak/Google/…, come back logged in" case (Spring Security's "OAuth2 Login", Quarkus' application-type=web-app): install(server) registers /login (redirects to the provider) and /callback (exchanges the code, verifies the id_token, starts the session) on a tinox.core.http_server.HttpServer — deliberately NOT on the annotation-driven auto-server, which can crash under allocation-heavy routes (issue #140), which this flow (JSON, a JWKS fetch, AES) is throughout. There is no server-side session store: both the short-lived "pending login" state (PKCE verifier + CSRF state, between /login and /callback) and the session itself (the verified claims) live entirely in httpOnly cookies, AES-256-GCM sealed (Crypto::aesEncrypt/ aesDecrypt) with a secret you provide — a tampered cookie or the wrong secret makes aesDecrypt throw, so the cookie is rejected rather than accepted. This also means sessions survive a server restart and work across multiple processes with no shared state.

Class / MethodSignatureDescription
OidcWebApp::new fn new(client: OidcClient, cookieSecret: String) -> OidcWebApp Throws if cookieSecret is empty (an empty secret is a well-known, guessable key, not "encryption off").
OidcWebApp::install fn install(server: HttpServer) -> Nothing Registers /login and /callback (paths configurable via loginPath/callbackPath) on server. Call once at startup, before server.listen().
OidcWebApp::requireLogin fn requireLogin(ctx: HttpContext) -> Bool Session gate for a route: on a missing/tampered/expired session cookie, redirects to loginPath and returns false (caller must return; immediately), otherwise true.
OidcWebApp::currentUser fn currentUser(ctx: HttpContext) -> Map<String, JsonValue> The verified claims of the current session — meaningful right after a successful requireLogin in the same request.
OidcWebApp::logout fn logout(ctx: HttpContext) -> Nothing Clears the session cookie and redirects to loginPath.

Example

import tinox.core.oidc;
import tinox.core.http_server;

class Main
{
    fnc main() -> Int32
    {
        let client: OidcClient = OidcClient::new(
            "https://accounts.google.com",
            "https://accounts.google.com/o/oauth2/v2/auth",
            "https://oauth2.googleapis.com/token",
            "https://www.googleapis.com/oauth2/v3/certs",
            "my-client-id",
            "my-client-secret",
            "https://my-app.example.com/callback"
        );
        let app: OidcWebApp = OidcWebApp::new(client, "my-cookie-secret");

        let server: HttpServer = HttpServer::new(8096);
        app.install(server);

        server.get("/", ctx => {
            if !app.requireLogin(ctx) { return; }
            let claims: Map<String, JsonValue> = app.currentUser(ctx);
            ctx.response.text("Hello, " + claims["sub"].getString());
        });

        server.get("/logout", ctx => { app.logout(ctx); });

        server.listen();
        return 0;
    }
}
  

A complete example, verified against a real Keycloak instance, lives at examples/keycloak_oidc_api/src/WebLogin.tnx.

json

import tinox.core.json;

The json module provides classes and annotations to parse, serialize, and type-safely process JSON data. With the @JsonSerializable annotation, the compiler automatically generates toJson() and fromJson() methods for arbitrary classes. The Json class serves as a central facade, while JsonValue represents a parsed JSON node and enables type queries and value access.

Classes & Methods

Class / MethodSignatureDescription
Json::parse fnc parse(text: String) -> JsonValue Parses a JSON string and returns a JsonValue node.
Json::stringify fnc stringify(value: JsonValue) -> String Serializes a JsonValue back into a JSON string.
Json::getField fnc getField(obj: JsonValue, key: String) -> JsonValue Reads a named field from a JSON object.
Json::serialize fnc serialize<T>(obj: T) -> String Calls obj.toJson() and returns the resulting JSON string. Requires @JsonSerializable on the class.
Json::deserialize fnc deserialize<T>(text: String) -> T Parses the JSON string and constructs an instance of type T via T::fromJson().
Json::intArrayFromJson fnc intArrayFromJson(arr: JsonValue) -> List<Int64> Converts a JSON array into a Tinox list of Int64 values.
Json::intArrayToString fnc intArrayToString(arr: List<Int64>) -> String Serializes an Int64 list as a JSON array string.
Json::intArrayWrap fnc intArrayWrap(key: String, arr: List<Int64>) -> String Wraps an Int64 list as a named JSON object, e.g. {"ids":[1,2,3]}.
JsonValue::getString fn getString() -> String Returns the value as a String.
JsonValue::getInt fn getInt() -> Int64 Returns the value as Int64.
JsonValue::getFloat fn getFloat() -> Float64 Returns the value as Float64.
JsonValue::getBool fn getBool() -> Bool Returns the value as Bool.
JsonValue::isNull fn isNull() -> Bool Checks whether the value is null.
JsonValue::isString / isInt / isFloat / isBool / isObject / isArray fn isX() -> Bool Type-checking methods for each JSON type.
JsonValue::getField fn getField(key: String) -> JsonValue Reads a field of the JSON object by its name.
JsonValue::len fn len() -> Int64 Number of elements, if the value is a JSON array (0 otherwise).
JsonValue::get fn get(index: Int64) -> JsonValue Reads an array element by index (hard error on an out-of-range index).
JsonValue::asList fn asList() -> List<JsonValue> Materializes a JSON array as a List<JsonValue> (issue #138, e.g. for JWKS' keys array in the jwt module).

Example

import tinox.core.json;

@JsonSerializable
class Product
{
    @JsonField("product_id")
    var id: Int64;
    var name: String;
    var price: Float64;
}

class Main
{
    fnc main() -> Int32
    {
        // Serialize object
        let p: Produkt = Produkt { id: 42, name: "Pen", price: 1.99 };
        let jsonStr: String = Json::serialize(p);
        println(jsonStr);

        // Parse JSON and read fields
        let raw: String = "{\"title\":\"Test\",\"value\":7}";
        let v: JsonValue = Json::parse(raw);
        let title: String = v.getField("title").getString();
        let value: Int64 = v.getField("value").getInt();
        println(title + " = " + value.toString());

        // Int array round-trip
        let ids: List = [1, 2, 3];
        let wrapped: String = Json::intArrayWrap("ids", ids);
        println(wrapped);

        // Deserialize
        let p2: Produkt = Json::deserialize(jsonStr);
        println(p2.name);
        return 0;
    }
}
  

msgpack

import tinox.core.msgpack;

Compact binary serialization (MessagePack), a binary counterpart to json's text-based JsonValue tree — useful for AMQP message payloads, inter-service RPC, or just storing structured data more compactly than JSON. No schema/codegen step, same self-describing dynamic-value model as JSON (object/array/ string/int/float/bool/null). MsgpackValue mirrors JsonValue's API shape (isString/getString, isInt/getInt, isArray/asArray, isObject/getField, ...) but — unlike the C-backed JsonValue — is a plain Tinox value: construct one directly via a struct literal for encoding, e.g. MsgpackValue { kind: "string", stringValue: "hi" }.

Encoding always picks the most compact representation that fits (fixint/fixstr/fixarray/fixmap where possible, otherwise the smallest width that holds the value). Decoding handles every core MessagePack type an external encoder could legitimately produce (all int widths signed/unsigned, both float widths, all string/array/map length-prefix widths) — verified against Python's independent msgpack library in both directions, and fuzz-tested (fuzz/msgpack/).

v1 scope: no bin8/16/32 (raw byte strings) or the ext family (application-specific extension types) — neither has a JsonValue-model equivalent to represent it as. A decoded map with a non-string key fails the whole decode (this module's object model only supports string keys, matching JsonValue). A decoded uint64 value ≥ 2⁶³ comes out as a negative Int64 (the same 64 raw bits reinterpreted as signed) — Tinox's Int64 has no wider/unsigned counterpart to hold the full range.

Classes & Methods

Class / MethodSignatureDescription
Msgpack::encode fn encode(value: MsgpackValue) -> List<Int64> Encodes a value tree to MessagePack bytes.
Msgpack::decode fn decode(bytes: List<Int64>) -> MsgpackValue Decodes MessagePack bytes into a value tree. Never crashes or hangs on truncated/malformed input (matching this stdlib's other self-contained-buffer parsers, it doesn't expose a separate success/failure flag either — a malformed input decodes to whatever partial/null result naturally falls out).
MsgpackValue isNull/isBool/isInt/isFloat/isString/isArray/isObject() -> Bool Type checks.
MsgpackValue getBool/getInt/getFloat/getString() -> ... Scalar accessors.
MsgpackValue asArray() -> List<MsgpackValue>, asMap() -> Map<String, MsgpackValue> Container accessors.
MsgpackValue::getField fn getField(key: String) -> MsgpackValue Reads an object field; returns a null-kind value if this isn't an object or the key is absent.

Example

import tinox.core.msgpack;

var fields: Map<String, MsgpackValue> = Map::new();
fields["name"] = MsgpackValue { kind: "string", stringValue: "Tinox" };
fields["version"] = MsgpackValue { kind: "int", intValue: 2 };

let root: MsgpackValue = MsgpackValue { kind: "object", objectValue: fields };
let bytes: List<Int64> = Msgpack::encode(root);

let decoded: MsgpackValue = Msgpack::decode(bytes);
println(decoded.getField("name").getString());

xml

import tinox.core.xml;

The xml module enables parsing, traversing, and rendering XML documents. Xml is the central facade class; XmlNode represents a single node in the XML tree. Attributes, text content, and child nodes are accessible via methods. XmlParser and XmlRenderer provide the internal parsing and output logic, while XmlQuery supports XPath-like queries.

Classes & Methods

Class / MethodSignatureDescription
Xml::parse fn parse(text: String) -> XmlNode Parses an XML string and returns the root node.
Xml::stringify fn stringify(node: XmlNode) -> String Renders an XmlNode tree back into an XML string.
Xml::query fn query(node: XmlNode, xpath: String) -> List<XmlNode> Executes an XPath-like query on the node tree and returns all matches.
XmlNode::tagName fn tagName() -> String Returns the tag name of the node (e.g. "person").
XmlNode::textContent fn textContent() -> String Returns the text content of the node.
XmlNode::attr fn attr(name: String) -> String Reads the value of a named attribute.
XmlNode::children fn children() -> List<XmlNode> Returns all direct child nodes as a list.
XmlNode::firstChild fn firstChild(tag: String) -> XmlNode Returns the first child with the specified tag name; throws an error if none is found.
XmlParser::parse fn parse(text: String) -> XmlNode Internal method: trims the text and starts parsing a single node.
XmlRenderer::render fn render(node: XmlNode) -> String Internal method: recursively generates the XML string for the given node.

Example

import tinox.core.xml;

class Main
{
    fnc main() -> Int32
    {
        let document: String = "<catalog>" +
            "<book id=\"1\"><title>Tinox Basics</title></book>" +
            "<book id=\"2\"><title>Advanced Tinox</title></book>" +
            "</catalog>";

        let root: XmlNode = Xml::parse(document);
        println("Root tag: " + root.tagName());

        let children: List<XmlNode> = root.children();
        var i: Int64 = 0;
        while i < children.len()
        {
            let book: XmlNode = children[i];
            let id: String = book.attr("id");
            let title: XmlNode = book.firstChild("title");
            println("Book " + id + ": " + title.textContent());
            i = i + 1;
        }

        // Render back to XML string
        let output: String = Xml::stringify(root);
        println(output);
        return 0;
    }
}
  

yaml

import tinox.core.yaml;

The yaml module provides support for the YAML format via the classes Yaml, YamlValue, YamlParser, and YamlRenderer. YamlValue is a type-discrete value container with a kind field (e.g. "string", "int", "map", "list"). Output is indented, with each indentation level using two spaces.

Classes & Methods

Class / MethodSignatureDescription
Yaml::parse fn parse(content: String) -> YamlValue Parses a YAML string and returns a YamlValue.
Yaml::stringify fn stringify(value: YamlValue) -> String Renders a YamlValue tree as an indented YAML string (starting indentation 0).
YamlValue::isString fn isString() -> Bool Checks whether the value is a string.
YamlValue::isInt fn isInt() -> Bool Checks whether the value is an integer.
YamlValue::isFloat fn isFloat() -> Bool Checks whether the value is a floating-point number.
YamlValue::isBool fn isBool() -> Bool Checks whether the value is a boolean.
YamlValue::isList fn isList() -> Bool Checks whether the value is a list.
YamlValue::isMap fn isMap() -> Bool Checks whether the value is a map (key-value pairs).
YamlRenderer::render fn render(value: YamlValue, indent: Int64) -> String Internal method: renders a YamlValue recursively with the specified indentation level.
YamlRenderer::indent fn indent(level: Int64) -> String Returns an indentation string with level * 2 spaces.

Example

import tinox.core.yaml;

class Main
{
    fnc main() -> Int32
    {
        // Build YamlValue tree manually
        let name: YamlValue = YamlValue {
            kind: "string",
            stringValue: "Tinox"
        };
        let version: YamlValue = YamlValue {
            kind: "int",
            intValue: 2
        };

        let m: Map<String, YamlValue> = Map::new();
        m["name"] = name;
        m["version"] = version;

        let root: YamlValue = YamlValue {
            kind: "map",
            mapValue: m
        };

        // Output as YAML string
        let yamlStr: String = Yaml::stringify(root);
        println(yamlStr);

        // Type checks
        println(name.isString().toString());  // true
        println(version.isInt().toString());  // true
        println(root.isMap().toString());     // true

        // Re-parse (returns empty map as placeholder)
        let parsed: YamlValue = Yaml::parse(yamlStr);
        println(parsed.isMap().toString());
        return 0;
    }
}
  

csv

import tinox.core.csv;

The csv module provides the Csv class for reading and writing comma-separated values (CSV). The parser correctly handles fields enclosed in quotation marks and commas within quotation marks. When serializing, fields with commas or quotation marks are automatically quoted correctly.

Classes & Methods

Class / MethodSignatureDescription
Csv::parse fn parse(content: String) -> List<List<String>> Parses a complete CSV text and returns it as a two-dimensional list. Empty lines are skipped.
Csv::parseRow fn parseRow(line: String) -> List<String> Parses a single CSV line character by character and correctly handles quotation marks.
Csv::toString fn toString(data: List<List<String>>) -> String Serializes a two-dimensional list back into a CSV string with line breaks.
Csv::rowToString fn rowToString(row: List<String>) -> String Serializes a single row. Fields with commas or " are automatically quoted; contained " are doubled.

Example

import tinox.core.csv;

class Main
{
    fnc main() -> Int32
    {
        let csvText: String =
            "name,age,city\n" +
            "Anna,30,Berlin\n" +
            "\"Müller, Klaus\",45,\"Frankfurt am Main\"\n" +
            "Lea,22,Hamburg\n";

        let tabelle: List<List<String>> = Csv::parse(csvText);

        // Print header row
        let header: List<String> = tabelle[0];
        println(header[0] + " | " + header[1] + " | " + header[2]);

        // Print data rows
        var i: Int64 = 1;
        while i < tabelle.len()
        {
            let row: List<String> = tabelle[i];
            println(row[0] + " is " + row[1] + " years old, lives in " + row[2]);
            i = i + 1;
        }

        // Add new row and serialize back
        let newRow: List<String> = ["Tom", "28", "Munich"];
        tabelle.push(newRow);
        let output: String = Csv::toString(tabelle);
        println(output);
        return 0;
    }
}
  

toml

import tinox.core.toml;

The toml module enables reading TOML configuration files. TomlParser reads sections ([section]), key-value pairs, and automatically recognizes the types String, Bool, Float, and Int. TomlValue is a polymorphic value container with type-checking and access methods. Rendering (writing) of TOML is prepared as an extension point.

Classes & Methods

Class / MethodSignatureDescription
Toml::parse fn parse(content: String) -> TomlValue Parses a TOML string and returns a TomlValue table.
Toml::stringify fn stringify(value: TomlValue) -> String Serializes a TomlValue tree into a TOML string (extension point).
TomlValue::isString fn isString() -> Bool Checks whether the value is a string.
TomlValue::isInt fn isInt() -> Bool Checks whether the value is an integer.
TomlValue::isFloat fn isFloat() -> Bool Checks whether the value is a floating-point number.
TomlValue::isBool fn isBool() -> Bool Checks whether the value is a boolean.
TomlValue::isTable fn isTable() -> Bool Checks whether the value is a TOML table (section).
TomlValue::isArray fn isArray() -> Bool Checks whether the value is a TOML array.
TomlValue::asString fn asString() -> String Returns the value as a String.
TomlValue::asInt fn asInt() -> Int64 Returns the value as Int64.
TomlValue::asFloat fn asFloat() -> Float64 Returns the value as Float64.
TomlValue::asBool fn asBool() -> Bool Returns the value as Bool.
TomlValue::asTable fn asTable() -> Map<String, TomlValue> Returns the contained table as a map.
TomlValue::asArray fn asArray() -> List<TomlValue> Returns the contained array as a list.
TomlParser::parseValue fn parseValue(value: String) -> TomlValue Automatically detects the type of a raw TOML value (String, Bool, Float, Int).

Example

import tinox.core.toml;

class Main
{
    fnc main() -> Int32
    {
        let konfigText: String =
            "debug = true\n" +
            "port = 8080\n" +
            "host = \"localhost\"\n" +
            "\n" +
            "[datenbank]\n" +
            "url = \"postgres://localhost/mydb\"\n" +
            "pool = 5\n";

        let config: TomlValue = Toml::parse(konfigText);

        // Wurzel-Tabelle auslesen
        let tabelle: Map<String, TomlValue> = config.asTable();

        let port: Int64 = tabelle["port"].asInt();
        let host: String = tabelle["host"].asString();
        let debug: Bool = tabelle["debug"].asBool();

        println("Host: " + host + ", Port: " + port.toString());
        println("Debug: " + debug.toString());

        // Untersektion
        let db: Map<String, TomlValue> = tabelle["datenbank"].asTable();
        let dbUrl: String = db["url"].asString();
        let pool: Int64 = db["pool"].asInt();
        println("DB: " + dbUrl + " (Pool: " + pool.toString() + ")");
        return 0;
    }
}
  

ini

import tinox.core.ini;

The ini module processes classic INI configuration files. Comment lines starting with ; or #, as well as empty lines, are ignored during parsing. Sections are recognized as [Name]. IniConfig holds all sections as a nested map and provides type-safe read and write methods with default values.

Classes & Methods

Class / MethodSignatureDescription
Ini::parse fn parse(content: String) -> IniConfig Parses an INI string and returns an IniConfig object.
Ini::stringify fn stringify(config: IniConfig) -> String Serializes an IniConfig object back into an INI-formatted string.
IniConfig::new fn new() -> IniConfig Creates a new, empty IniConfig instance.
IniConfig::getString fn getString(config: IniConfig, section: String, key: String, default: String) -> String Reads a string value from the specified section. Returns default if the section or key is missing.
IniConfig::getInt fn getInt(config: IniConfig, section: String, key: String, default: Int64) -> Int64 Reads an integer value. Returns default if the key does not exist.
IniConfig::setString fn setString(config: IniConfig, section: String, key: String, value: String) -> Nothing Sets a string value in the section. Automatically creates the section if it does not yet exist.

Example

import tinox.core.ini;

class Main
{
    fnc main() -> Int32
    {
        let iniText: String =
            "; Server configuration\n" +
            "[server]\n" +
            "host=127.0.0.1\n" +
            "port=3000\n" +
            "\n" +
            "[logging]\n" +
            "level=info\n" +
            "# Maximum file size in MB\n" +
            "maxsize=10\n";

        let config: IniConfig = Ini::parse(iniText);

        // Read values
        let host: String = IniConfig::getString(config, "server", "host", "localhost");
        let port: Int64 = IniConfig::getInt(config, "server", "port", 80);
        let level: String = IniConfig::getString(config, "logging", "level", "warn");

        println("Server: " + host + ":" + port.toString());
        println("Log level: " + level);

        // Add value
        IniConfig::setString(config, "server", "timeout", "30");

        // Serialize back
        let output: String = Ini::stringify(config);
        println(output);
        return 0;
    }
}
  

base64

import tinox.core.base64;

The base64 module provides functions for Base64 encoding and decoding of strings and byte lists. Base64::encode and Base64::decode delegate to native runtime functions (base64Encode / base64Decode), while encodeBytes encodes a list of integer bytes character by character. The format is suitable for transmitting binary data in text-based protocols.

Classes & Methods

Class / MethodSignatureDescription
Base64::encode fn encode(data: String) -> String Encodes a string as a Base64 string via the native runtime function.
Base64::decode fn decode(encoded: String) -> String Decodes a Base64 string back into the original string.
Base64::encodeBytes fn encodeBytes(data: List<Int64>) -> String Encodes a list of byte values (Int64) character by character as a Base64 string.
Base64::encodeUrlBytes fn encodeUrlBytes(data: List<Int64>) -> String Like encodeBytes, but with the URL-/filename-safe alphabet (-/_ instead of +//) and without = padding (RFC 4648 §5) — e.g. for PKCE code_verifier/code_challenge in the oauth2 module (issue #131).
Base64::decodeUrlBytes fn decodeUrlBytes(encoded: String) -> List<Int64> Decodes base64url (RFC 4648 §5, no padding expected) as a raw byte list — e.g. for JWT segments (RFC 7515 §3) and JWKS n/e fields (RFC 7517 §9.3) in the jwt module (issue #138).

Example

import tinox.core.base64;

class Main
{
    fnc main() -> Int32
    {
        // Simple string round-trip
        let original: String = "Hello Tinox!";
        let encoded: String = Base64::encode(original);
        println("Encoded:   " + encoded);

        let decoded: String = Base64::decode(encoded);
        println("Decoded:   " + decoded);

        // Encode byte list
        let bytes: List<Int64> = [72, 101, 108, 108, 111];  // "Hello"
        let byteEncoded: String = Base64::encodeBytes(bytes);
        println("Bytes encoded: " + byteEncoded);

        // Use case: HTTP Basic Auth header
        let credentials: String = "user:secret";
        let authHeader: String = "Basic " + Base64::encode(credentials);
        println(authHeader);

        // Check that encode/decode is correctly reversible
        let test: String = "Special chars: äöü 123";
        let roundTrip: String = Base64::decode(Base64::encode(test));
        println(roundTrip);
        return 0;
    }
}
  

hex

import tinox.core.hex;

The hex module provides the Hex class that encodes and decodes strings to hexadecimal representations. Each byte is represented as two hex digits (lowercase). Additionally, Hex::dump provides a space-separated byte dump representation as known from debuggers. The internal helper methods nibbleToChar and charToNibble handle the conversion between nibble and character.

Classes & Methods

Class / MethodSignatureDescription
Hex::encode fn encode(data: String) -> String Encodes each character value of the string as two lowercase hexadecimal digits.
Hex::decode fn decode(hex: String) -> String Converts a hexadecimal string pairwise back into the original string.
Hex::dump fn dump(data: String) -> String Returns a space-separated hex dump representation of the string (e.g. "48 65 6c 6c 6f").
Hex::nibbleToChar fn nibbleToChar(n: Int64) -> String Converts a nibble (0–15) into the corresponding hex character ('0''9', 'a''f').
Hex::charToNibble fn charToNibble(c: String) -> Int64 Converts a single hex character (0–9, a–f, A–F) into its numeric value (0–15).
Hex::decodeBytes fn decodeBytes(hex: String) -> List<Int64> Like decode, but returns the raw bytes as a List<Int64> instead of a Tinox String — important when an embedded 0-byte in the digest would otherwise be silently truncated by the string route (e.g. fromCharCode), such as when converting a Crypto::sha256 hex digest back into raw bytes (see the oauth2 module, issue #131).

Example

import tinox.core.hex;

class Main
{
    fnc main() -> Int32
    {
        let text: String = "Hello";

        // Encode string as hex
        let hexEncoded: String = Hex::encode(text);
        println("Hex:  " + hexEncoded);   // 48656c6c6f

        // Hex dump (space-separated)
        let dump: String = Hex::dump(text);
        println("Dump: " + dump);         // 48 65 6c 6c 6f

        // Decode back
        let original: String = Hex::decode(hexEncoded);
        println("Original: " + original); // Hello

        // Individual nibble conversions
        let c: String = Hex::nibbleToChar(10);  // "a"
        let n: Int64 = Hex::charToNibble("f");  // 15
        println("10 -> " + c + ", f -> " + n.toString());

        // Use case: check hash output
        let hashBytes: String = "\xde\xad\xbe\xef";
        println("Hash hex: " + Hex::encode(hashBytes));
        return 0;
    }
}
  

encoding

import tinox.core.encoding;

The encoding module provides basic character encoding helper methods: conversion between characters and numeric code points, UTF-8 byte list processing, and HTML entity encoding and decoding. The Encoding class is built as a pure facade — all methods operate statelessly on their inputs.

Classes & Methods

Class / MethodSignatureDescription
Encoding::toCharCode fn toCharCode(s: String) -> Int64 Returns the numeric code point of the first character of the string. Empty string yields 0.
Encoding::fromCharCode fn fromCharCode(code: Int64) -> String Converts a numeric code point into the corresponding character as a string.
Encoding::toUtf8 fn toUtf8(s: String) -> List<Int64> Returns the code points of all characters of the string as an Int64 list.
Encoding::fromUtf8 fn fromUtf8(codes: List<Int64>) -> String Builds a string from a list of code points.
Encoding::htmlEncode fn htmlEncode(s: String) -> String Replaces the HTML special characters &, <, >, ", and ' with their corresponding HTML entities.
Encoding::htmlDecode fn htmlDecode(s: String) -> String Converts HTML entities (&amp;, &lt;, &gt;, &quot;, &#39;) back into their original characters.

Example

import tinox.core.encoding;

class Main
{
    fnc main() -> Int32
    {
        // Codepoint-Konvertierungen
        let code: Int64 = Encoding::toCharCode("A");
        println("'A' hat Codepoint: " + code.toString());  // 65

        let zeichen: String = Encoding::fromCharCode(9731); // ☃
        println("Codepoint 9731: " + zeichen);

        // UTF-8 Byte-Liste
        let text: String = "Hi!";
        let bytes: List<Int64> = Encoding::toUtf8(text);
        println("Bytes: " + bytes[0].toString() + " " +
                            bytes[1].toString() + " " +
                            bytes[2].toString());  // 72 105 33

        let restored: String = Encoding::fromUtf8(bytes);
        println("Restored: " + restored);  // Hi!

        // HTML encoding for safe browser output
        let userInput: String = "<script>alert(\"XSS\")</script>";
        let safe: String = Encoding::htmlEncode(userInput);
        println(safe);

        // HTML decoding
        let entities: String = "&lt;b&gt;Bold&lt;/b&gt;";
        let original: String = Encoding::htmlDecode(entities);
        println(original);
        return 0;
    }
}
  

array

import tinox.core.array;

The array module provides the helper class Arrays that encapsulates frequently needed operations on List<Int64>: searching, statistics (min, max, sum, average), transformations (reverse, filter), sorting via bubble sort, and generating ranges and prefilled lists.

Classes & Methods

Class / MethodSignatureDescription
Arrays::containsInt fn containsInt(arr: List<Int64>, value: Int64) -> Bool Returns true if value is contained in arr.
Arrays::indexOfInt fn indexOfInt(arr: List<Int64>, value: Int64) -> Int64 Returns the index of the first occurrence of value, or -1 if not found.
Arrays::findMin fn findMin(arr: List<Int64>) -> Int64 Returns the smallest element in arr. The array must not be empty.
Arrays::findMax fn findMax(arr: List<Int64>) -> Int64 Returns the largest element in arr. The array must not be empty.
Arrays::sum fn sum(arr: List<Int64>) -> Int64 Calculates the sum of all elements.
Arrays::average fn average(arr: List<Int64>) -> Int64 Calculates the integer average (sum / length, truncated). Array must not be empty.
Arrays::reverseInt fn reverseInt(arr: List<Int64>) -> List<Int64> Returns a new list with the elements in reversed order.
Arrays::filterPositive fn filterPositive(arr: List<Int64>) -> List<Int64> Returns a new list containing only elements with value > 0.
Arrays::bubbleSort fn bubbleSort(arr: List<Int64>) -> List<Int64> Sorts the list in ascending order using bubble sort and returns the result.
Arrays::isSorted fn isSorted(arr: List<Int64>) -> Bool Checks whether the elements are in non-descending order.
Arrays::range fn range(start: Int64, end: Int64) -> List<Int64> Generates a list with values start, start+1, …, end-1 (exclusive end).
Arrays::fill fn fill(value: Int64, count: Int64) -> List<Int64> Creates a list with count copies of value.

Example

import tinox.core.array;

class Main
{
    fnc main() -> Int32
    {
        let zahlen: List<Int64> = [-3, 7, 2, -1, 5, 0, 9];

        let pos: List<Int64> = Arrays::filterPositive(zahlen);
        // pos = [7, 2, 5, 9]

        let sortiert: List<Int64> = Arrays::bubbleSort(pos);
        // sortiert = [2, 5, 7, 9]

        let min: Int64 = Arrays::findMin(sortiert);   // 2
        let max: Int64 = Arrays::findMax(sortiert);   // 9
        let s: Int64   = Arrays::sum(sortiert);       // 23
        let avg: Int64 = Arrays::average(sortiert);   // 5

        let idx: Int64 = Arrays::indexOfInt(sortiert, 7);  // 2
        let hat5: Bool = Arrays::containsInt(sortiert, 5); // true

        let rev: List<Int64> = Arrays::reverseInt(sortiert);
        // rev = [9, 7, 5, 2]

        let r: List<Int64>   = Arrays::range(1, 6);   // [1,2,3,4,5]
        let f: List<Int64>   = Arrays::fill(0, 4);    // [0,0,0,0]
        let ok: Bool         = Arrays::isSorted(sortiert); // true
        return 0;
    }
}

collections

import tinox.core.collections;

The collections module provides three generic data structures: a LIFO Stack<T>, a FIFO Queue<T>, and an ordered value pair Pair<T, U>. The factory class Collections contains shorthand methods for creating them.

Classes & Methods

Class / MethodSignatureDescription
Collections::newStack fn newStack<T>() -> Stack<T> Creates an empty stack.
Collections::newQueue fn newQueue<T>() -> Queue<T> Creates an empty queue.
Collections::newPair fn newPair<T, U>(first: T, second: U) -> Pair<T, U> Creates a new pair from two values.
Stack::new fn new() -> Stack<T> Creates an empty stack directly via the class.
Stack::push fn push(value: T) -> Nothing Pushes a value onto the top of the stack.
Stack::pop fn pop() -> T Removes and returns the top element. Throws an error on empty stack.
Stack::peek fn peek() -> T Returns the top element without removing it. Throws an error on empty stack.
Stack::isEmpty fn isEmpty() -> Bool Returns true if the stack is empty.
Stack::size fn size() -> Int64 Returns the number of elements in the stack.
Queue::new fn new() -> Queue<T> Creates an empty queue directly via the class.
Queue::enqueue fn enqueue(value: T) -> Nothing Inserts a value at the end of the queue.
Queue::dequeue fn dequeue() -> T Removes and returns the front element. Throws an error on empty queue.
Queue::peek fn peek() -> T Returns the front element without removing it. Throws an error on empty queue.
Queue::isEmpty fn isEmpty() -> Bool Returns true if the queue is empty.
Queue::size fn size() -> Int64 Returns the number of elements in the queue.
Pair::new fn new(first: T, second: U) -> Pair<T, U> Creates a pair with two values.
Pair::getFirst fn getFirst() -> T Returns the first value of the pair.
Pair::getSecond fn getSecond() -> U Returns the second value of the pair.

Example

import tinox.core.collections;

class Main
{
    fnc main() -> Int32
    {
        // Stack-Verwendung
        let s: Stack<Int64> = Stack<Int64>::new();
        s.push(10);
        s.push(20);
        s.push(30);
        let top: Int64 = s.peek();   // 30
        let popped: Int64 = s.pop();    // 30
        let groesse: Int64 = s.size(); // 2

        // Queue-Verwendung
        let q: Queue<String> = Collections::newQueue<String>();
        q.enqueue("alpha");
        q.enqueue("beta");
        q.enqueue("gamma");
        let erstes: String = q.dequeue(); // "alpha"
        let leer: Bool     = q.isEmpty(); // false

        // Pair-Verwendung
        let p: Pair<String, Int64> = Collections::newPair<String, Int64>("Alter", 42);
        let schluessel: String = p.getFirst();  // "Alter"
        let alter: Int64       = p.getSecond(); // 42
        return 0;
    }
}

heap

import tinox.core.heap;

The heap module implements a generic binary heap (Heap<T>) with a customizable comparator, as well as the pre-built specializations MinHeap<T> (smallest element first) and MaxHeap<T> (largest element first). The internal helper methods siftUp, siftDown, and buildHeap are also available.

Classes & Methods

Class / MethodSignatureDescription
Heap::new fn new() -> Heap<T> Creates an empty min-heap with the default comparator a < b.
Heap::newWithCompare fn newWithCompare(comparator: fnc(T, T) -> Bool) -> Heap<T> Creates a heap with a custom comparator function.
Heap::push fn push(heap: Heap<T>, item: T) -> Nothing Inserts an element into the heap and restores the heap property.
Heap::pop fn pop(heap: Heap<T>) -> T Removes and returns the root element (minimum/maximum). Throws an error on empty heap.
Heap::peek fn peek(heap: Heap<T>) -> T Returns the root element without removing it. Throws an error on empty heap.
Heap::siftUp fn siftUp(heap: Heap<T>, startIndex: Int64) -> Nothing Restores the heap property upward (used internally after push).
Heap::siftDown fn siftDown(heap: Heap<T>, startIndex: Int64) -> Nothing Restores the heap property downward (used internally after pop).
Heap::isEmpty fn isEmpty(heap: Heap<T>) -> Bool Returns true if the heap contains no elements.
Heap::size fn size(heap: Heap<T>) -> Int64 Returns the number of elements in the heap.
Heap::buildHeap fn buildHeap(heap: Heap<T>, items: List<T>) -> Nothing Builds the heap in O(n) from an existing list.
MinHeap::new fn new() -> MinHeap<T> Creates an empty min-heap (smallest element at the root).
MinHeap::push fn push(h: MinHeap<T>, item: T) -> Nothing Inserts an element into the min-heap.
MinHeap::pop fn pop(h: MinHeap<T>) -> T Removes and returns the smallest element.
MinHeap::peek fn peek(h: MinHeap<T>) -> T Returns the smallest element without removing it.
MaxHeap::new fn new() -> MaxHeap<T> Creates an empty max-heap (largest element at the root).
MaxHeap::push fn push(h: MaxHeap<T>, item: T) -> Nothing Inserts an element into the max-heap.
MaxHeap::pop fn pop(h: MaxHeap<T>) -> T Removes and returns the largest element.
MaxHeap::peek fn peek(h: MaxHeap<T>) -> T Returns the largest element without removing it.

Example

import tinox.core.heap;

class Main
{
    fnc main() -> Int32
    {
        // Min-Heap: kleinstes Element zuerst
        let min: MinHeap<Int64> = MinHeap<Int64>::new();
        MinHeap::push(min, 5);
        MinHeap::push(min, 1);
        MinHeap::push(min, 3);
        let smallest: Int64  = MinHeap::peek(min);  // 1
        let removed: Int64   = MinHeap::pop(min);   // 1
        let nextOne: Int64   = MinHeap::pop(min);   // 3

        // Max-heap: largest element first
        let max: MaxHeap<Int64> = MaxHeap<Int64>::new();
        MaxHeap::push(max, 5);
        MaxHeap::push(max, 1);
        MaxHeap::push(max, 8);
        let largest: Int64 = MaxHeap::pop(max);   // 8

        // Generic heap with custom comparator (min-heap for Float64)
        let cmp: fnc(Float64, Float64) -> Bool = fnc(a: Float64, b: Float64) -> Bool { return a < b; };
        let h: Heap<Float64> = Heap<Float64>::newWithCompare(cmp);
        Heap::push(h, 3.14);
        Heap::push(h, 1.41);
        let leer: Bool = Heap::isEmpty(h);         // false
        let anz: Int64 = Heap::size(h);            // 2

        // buildHeap: build heap from list
        let h2: Heap<Int64> = Heap<Int64>::new();
        Heap::buildHeap(h2, [9, 4, 7, 2, 6]);
        let root: Int64 = Heap::peek(h2);        // 2
        return 0;
    }
}

queue

import tinox.core.queue;

The queue module provides two specialized queue types. PriorityQueue<T> orders elements by an integer priority value (lower number = higher priority). CircularBuffer<T> is a ring-buffer-like buffer with fixed capacity that accepts no further elements when full.

Classes & Methods

Class / MethodSignatureDescription
PriorityQueue::new fn new() -> PriorityQueue<T> Creates an empty priority queue.
PriorityQueue::enqueue fn enqueue(q: PriorityQueue<T>, item: T, priority: Int64) -> Nothing Inserts item with the specified priority value (lower value = higher priority).
PriorityQueue::dequeue fn dequeue(q: PriorityQueue<T>) -> T Removes and returns the element with the highest priority. Throws an error on empty queue.
PriorityQueue::peek fn peek(q: PriorityQueue<T>) -> T Returns the element with the highest priority without removing it.
PriorityQueue::sort fn sort(q: PriorityQueue<T>) -> Nothing Sorts the internal list by priority (called automatically after each enqueue).
PriorityQueue::isEmpty fn isEmpty(q: PriorityQueue<T>) -> Bool Returns true if the queue is empty.
PriorityQueue::size fn size(q: PriorityQueue<T>) -> Int64 Returns the number of elements.
CircularBuffer::new fn new(size: Int64) -> CircularBuffer<T> Creates a circular buffer with the specified maximum capacity.
CircularBuffer::push fn push(buf: CircularBuffer<T>, item: T) -> Bool Inserts an element. Returns false if the buffer is full.
CircularBuffer::pop fn pop(buf: CircularBuffer<T>) -> T Removes and returns the oldest element. Throws an error on empty buffer.
CircularBuffer::isFull fn isFull(buf: CircularBuffer<T>) -> Bool Returns true if the buffer is full.
CircularBuffer::isEmpty fn isEmpty(buf: CircularBuffer<T>) -> Bool Returns true if the buffer is empty.

Example

import tinox.core.queue;

class Main
{
    fnc main() -> Int32
    {
        // Priority queue
        let pq: PriorityQueue<String> = PriorityQueue<String>::new();
        PriorityQueue::enqueue(pq, "Low",      10);
        PriorityQueue::enqueue(pq, "Critical",   1);
        PriorityQueue::enqueue(pq, "Medium",     5);

        let first: String  = PriorityQueue::dequeue(pq); // "Critical"
        let second: String  = PriorityQueue::dequeue(pq); // "Medium"
        let size: Int64     = PriorityQueue::size(pq);    // 1

        // Ring buffer with capacity 3
        let buf: CircularBuffer<Int64> = CircularBuffer<Int64>::new(3);
        let ok1: Bool = CircularBuffer::push(buf, 100); // true
        let ok2: Bool = CircularBuffer::push(buf, 200); // true
        let ok3: Bool = CircularBuffer::push(buf, 300); // true
        let ok4: Bool = CircularBuffer::push(buf, 400); // false – full

        let voll: Bool  = CircularBuffer::isFull(buf);  // true
        let wert: Int64 = CircularBuffer::pop(buf);     // 100
        let leer: Bool  = CircularBuffer::isEmpty(buf); // false
        return 0;
    }
}

set

import tinox.core.set;

The set module provides the generic class Set<T> that implements an ordered set without duplicates. In addition to basic operations (add, remove, contains check), the class supports classic set operations: union, intersection, difference, and subset check.

Classes & Methods

Class / MethodSignatureDescription
Set::new fn new() -> Set<T> Creates an empty set.
Set::add fn add(set: Set<T>, item: T) -> Nothing Adds item to the set if it is not already contained.
Set::remove fn remove(set: Set<T>, item: T) -> Bool Removes item from the set. Returns true if the element was present.
Set::contains fn contains(set: Set<T>, item: T) -> Bool Returns true if item is contained in the set.
Set::size fn size(set: Set<T>) -> Int64 Returns the number of elements in the set.
Set::isEmpty fn isEmpty(set: Set<T>) -> Bool Returns true if the set is empty.
Set::union fn union(a: Set<T>, b: Set<T>) -> Set<T> Returns the union of a and b (all elements from both).
Set::intersection fn intersection(a: Set<T>, b: Set<T>) -> Set<T> Returns the intersection (only elements present in both sets).
Set::difference fn difference(a: Set<T>, b: Set<T>) -> Set<T> Returns the difference set (elements in a that are not in b).
Set::isSubsetOf fn isSubsetOf(a: Set<T>, b: Set<T>) -> Bool Returns true if all elements of a are also contained in b.

Example

import tinox.core.set;

class Main
{
    fnc main() -> Int32
    {
        let a: Set<Int64> = Set<Int64>::new();
        Set::add(a, 1);
        Set::add(a, 2);
        Set::add(a, 3);
        Set::add(a, 2); // Duplikat – wird ignoriert

        let b: Set<Int64> = Set<Int64>::new();
        Set::add(b, 2);
        Set::add(b, 3);
        Set::add(b, 4);

        let u: Set<Int64> = Set::union(a, b);        // {1,2,3,4}
        let s: Set<Int64> = Set::intersection(a, b); // {2,3}
        let d: Set<Int64> = Set::difference(a, b);   // {1}

        let enthalten: Bool = Set::contains(a, 3);    // true
        let groesse: Int64  = Set::size(a);            // 3

        let c: Set<Int64> = Set<Int64>::new();
        Set::add(c, 2);
        Set::add(c, 3);
        let teilmenge: Bool = Set::isSubsetOf(c, a);  // true

        let entfernt: Bool  = Set::remove(a, 1);       // true
        let leer: Bool      = Set::isEmpty(a);         // false
        return 0;
    }
}

sort

import tinox.core.sort;

The sort module provides the Sort class with three classic sorting algorithms for List<Int64>: Quicksort (average O(n log n), in-place via partition), Merge Sort (stable, splits and merges recursively), and Insertion Sort (efficient for small or nearly sorted lists). All methods return the sorted list.

Classes & Methods

Class / MethodSignatureDescription
Sort::quickSort fn quickSort(arr: List<Int64>) -> List<Int64> Sorts the list in ascending order using quicksort. Delegates to the internal helper.
Sort::quickSortHelper fn quickSortHelper(arr: List<Int64>, low: Int64, high: Int64) -> List<Int64> Recursive quicksort on the sub-range [low, high].
Sort::partition fn partition(arr: List<Int64>, low: Int64, high: Int64) -> Int64 Lomuto partitioning: selects arr[high] as pivot and returns the pivot index.
Sort::mergeSort fn mergeSort(arr: List<Int64>) -> List<Int64> Sorts the list in ascending order using merge sort (stable algorithm).
Sort::mergeSortHelper fn mergeSortHelper(arr: List<Int64>) -> List<Int64> Recursive merge sort helper: splits the list in two halves and merges them.
Sort::mergeInto fn mergeInto(result: List<Int64>, left: List<Int64>, right: List<Int64>) -> List<Int64> Merges two sorted sublists into the target list result.
Sort::insertionSort fn insertionSort(arr: List<Int64>) -> List<Int64> Sorts the list in ascending order using insertion sort. Especially efficient for small or nearly sorted lists.

Example

import tinox.core.sort;

class Main
{
    fnc main() -> Int32
    {
        let daten: List<Int64> = [64, 25, 12, 22, 11];

        // Quicksort
        let qs: List<Int64> = Sort::quickSort(daten);
        // qs = [11, 12, 22, 25, 64]

        // Merge-Sort
        let ms: List<Int64> = Sort::mergeSort(daten);
        // ms = [11, 12, 22, 25, 64]

        // Insertion sort (good for small lists)
        let small: List<Int64> = [5, 3, 1, 4, 2];
        let is: List<Int64> = Sort::insertionSort(small);
        // is = [1, 2, 3, 4, 5]

        // Manual partition for custom algorithms
        let arr: List<Int64> = [3, 6, 8, 10, 1, 2, 1];
        let pivotIdx: Int64 = Sort::partition(arr, 0, arr.len() - 1);
        return 0;
    }
}

iter

import tinox.core.iter;

The iter module provides functional iteration tools. The Iter class offers methods for generating number and repetition sequences (range, rangeStep, repeat, cycle), as well as transformation and aggregation methods (map, filter, reduce, enumerate, zip). The Iterator class encapsulates the state of a running iterator with hasNext / next.

Classes & Methods

Class / MethodSignatureDescription
Iter::range fn range(start: Int64, end: Int64) -> Iterator Creates an iterator over [start, end) with step size 1.
Iter::rangeStep fn rangeStep(start: Int64, end: Int64, step: Int64) -> Iterator Creates an iterator over [start, end) with a custom step size.
Iter::repeat fn repeat<T>(value: T, count: Int64) -> List<T> Returns a list with count copies of value.
Iter::cycle fn cycle<T>(list: List<T>) -> Iterator Creates an infinitely cyclic iterator over the given list.
Iter::enumerate fn enumerate<T>(list: List<T>) -> List<Pair<Int64, T>> Returns a list of (index, value) pairs.
Iter::zip fn zip<T, U>(a: List<T>, b: List<U>) -> List<Pair<T, U>> Joins two lists element-wise into pairs. The length corresponds to the shorter list.
Iter::map fn map<T, U>(list: List<T>, transform: fnc(T) -> U) -> List<U> Applies a transformation function to each element and returns the new list.
Iter::filter fn filter<T>(list: List<T>, predicate: fnc(T) -> Bool) -> List<T> Returns a new list containing only elements for which the predicate returns true.
Iter::reduce fn reduce<T, U>(list: List<T>, initial: U, combiner: fnc(U, T) -> U) -> U Folds the list with a combiner function into a single value.
Iterator::hasNext fn hasNext() -> Bool Returns true if the iterator can still deliver more elements (always true for cyclic iterators).
Iterator::next fn next() -> Int64 Returns the next element and advances the pointer.

Example

import tinox.core.iter;

class Main
{
    fnc main() -> Int32
    {
        // Number range with step
        let it: Iterator = Iter::rangeStep(0, 10, 2);
        while it.hasNext()
        {
            let n: Int64 = it.next(); // 0, 2, 4, 6, 8
        }

        // map: square numbers
        let nums: List<Int64> = [1, 2, 3, 4, 5];
        let squares: List<Int64> = Iter::map<Int64, Int64>(nums, fnc(x: Int64) -> Int64 { return x * x; });
        // [1, 4, 9, 16, 25]

        // filter: only even numbers
        let even: List<Int64> = Iter::filter<Int64>(nums, fnc(x: Int64) -> Bool { return x % 2 == 0; });
        // [2, 4]

        // reduce: sum
        let sum: Int64 = Iter::reduce<Int64, Int64>(nums, 0, fnc(acc: Int64, x: Int64) -> Int64 { return acc + x; });
        // 15

        // enumerate
        let pairs: List<Pair<Int64, Int64>> = Iter::enumerate<Int64>(nums);
        // [(0,1),(1,2),(2,3),(3,4),(4,5)]

        // zip
        let letters: List<String> = ["a", "b", "c"];
        let combined = Iter::zip<Int64, String>(nums, letters);
        // [(1,"a"),(2,"b"),(3,"c")]

        // repeat
        let zeros: List<Int64> = Iter::repeat<Int64>(0, 5);
        // [0, 0, 0, 0, 0]
        return 0;
    }
}

graph

import tinox.core.graph;

The graph module implements a generic directed graph Graph<V>, where nodes are identified by String identifiers and edges carry a Float64 weight. Included are breadth-first search (BFS), depth-first search (DFS), and the Dijkstra algorithm for computing shortest paths. The helper class GraphEdge describes a weighted edge.

Classes & Methods

Class / MethodSignatureDescription
Graph::new fn new() -> Graph<V> Creates an empty directed graph.
Graph::addNode fn addNode(graph: Graph<V>, id: String, value: V) -> Nothing Adds a node with the identifier id and value value. Initializes the adjacency list if not already present.
Graph::addEdge fn addEdge(graph: Graph<V>, from: String, to: String, weight: Float64) -> Nothing Adds a directed edge from from to to with weight weight.
Graph::neighbors fn neighbors(graph: Graph<V>, id: String) -> List<GraphEdge> Returns all outgoing edges of node id.
Graph::bfs fn bfs(graph: Graph<V>, start: String, visitor: fnc(String) -> Nothing) -> Nothing Performs a breadth-first search from start and calls visitor for each visited node.
Graph::dfs fn dfs(graph: Graph<V>, start: String, visitor: fnc(String) -> Nothing) -> Nothing Performs a depth-first search from start and calls visitor for each visited node.
Graph::dfsHelper fn dfsHelper(graph: Graph<V>, current: String, visited: Map<String, Bool>, visitor: fnc(String) -> Nothing) -> Nothing Internal recursive helper for depth-first search.
Graph::dijkstra fn dijkstra(graph: Graph<V>, start: String, end: String) -> Float64 Computes the shortest path between start and end. Returns the distance, or -1.0 if no path exists.

Example

import tinox.core.graph;

class Main
{
    fnc main() -> Int32
    {
        let g: Graph<String> = Graph<String>::new();

        Graph::addNode(g, "A", "City A");
        Graph::addNode(g, "B", "City B");
        Graph::addNode(g, "C", "City C");
        Graph::addNode(g, "D", "City D");

        Graph::addEdge(g, "A", "B", 4.0);
        Graph::addEdge(g, "A", "C", 2.0);
        Graph::addEdge(g, "C", "B", 1.0);
        Graph::addEdge(g, "B", "D", 5.0);
        Graph::addEdge(g, "C", "D", 8.0);

        // Breadth-first search from A
        Graph::bfs(g, "A", fnc(id: String) -> Nothing {
            // Visit order: A, B, C, D
        });

        // Depth-first search from A
        Graph::dfs(g, "A", fnc(id: String) -> Nothing {
            // Visit order (depth-first): A, B, D, C
        });

        // Dijkstra: shortest path from A to D
        let distance: Float64 = Graph::dijkstra(g, "A", "D");
        // A->C (2.0) + C->B (1.0) + B->D (5.0) = 8.0

        // Get neighbors of C
        let nb: List<GraphEdge> = Graph::neighbors(g, "C");
        // nb[0].to = "B", nb[0].weight = 1.0
        return 0;
    }
}

trie

import tinox.core.trie;

The trie module implements a prefix tree (trie) for efficient storage and search of strings. In addition to exact word search and prefix check, the trie provides an autocomplete function that lists all words stored in the tree with a given prefix. The internal node structure TrieNode manages child nodes as Map<String, TrieNode>.

Classes & Methods

Class / MethodSignatureDescription
Trie::new fn new() -> Trie Creates an empty trie with an empty root node.
Trie::insert fn insert(trie: Trie, word: String) -> Nothing Inserts the word word character by character into the trie and marks the end.
Trie::search fn search(trie: Trie, word: String) -> Bool Returns true if word is fully contained in the trie.
Trie::startsWith fn startsWith(trie: Trie, prefix: String) -> Bool Returns true if at least one stored word starts with prefix.
Trie::autocomplete fn autocomplete(trie: Trie, prefix: String) -> List<String> Returns all words stored in the trie that start with prefix.
Trie::collectWords fn collectWords(node: TrieNode, prefix: String) -> List<String> Recursive helper: collects all complete words below node and appends them to prefix.
TrieNode::new fn new() -> TrieNode Creates a new node with empty child map and isEnd = false.

Example

import tinox.core.trie;

class Main
{
    fnc main() -> Int32
    {
        let t: Trie = Trie::new();

        // Insert words
        Trie::insert(t, "apfel");
        Trie::insert(t, "apfelbaum");
        Trie::insert(t, "aprikose");
        Trie::insert(t, "banane");
        Trie::insert(t, "birne");

        // Exact search
        let found: Bool    = Trie::search(t, "apfel");     // true
        let missing: Bool  = Trie::search(t, "apfelkuchen"); // false

        // Prefix check
        let hatAp: Bool     = Trie::startsWith(t, "ap");    // true
        let hatXy: Bool     = Trie::startsWith(t, "xy");    // false

        // Autocomplete
        let suggestions: List<String> = Trie::autocomplete(t, "ap");
        // ["apfel", "apfelbaum", "aprikose"] (order depends on map keys)

        let bSuggestions: List<String> = Trie::autocomplete(t, "b");
        // ["banane", "birne"]

        let noSuggestions: List<String> = Trie::autocomplete(t, "z");
        // []
        return 0;
    }
}

math

import tinox.core.math;

The math module provides basic mathematical operations for integer values. It includes mathematical constants (π, e, τ), comparison and absolute value operations, power and factorial calculation, and a simple pseudo-random number generator based on a linear congruential generator (LCG).

Classes & Methods

Class / MethodSignatureDescription
Math::pi fn pi() -> Float64 Returns the mathematical constant π (3.14159…).
Math::e fn e() -> Float64 Returns Euler's number e (2.71828…).
Math::tau fn tau() -> Float64 Returns τ = 2 · π (6.28318…).
Math::abs fn abs(x: Int64) -> Int64 Returns the absolute value of x (always ≥ 0).
Math::min fn min(a: Int64, b: Int64) -> Int64 Returns the smaller of the two values.
Math::max fn max(a: Int64, b: Int64) -> Int64 Returns the larger of the two values.
Math::square fn square(x: Int64) -> Int64 Returns x² (x · x).
Math::pow fn pow(base: Int64, exp: Int64) -> Int64 Calculates base to the power of exp (exp must be ≥ 0).
Math::factorial fn factorial(n: Int64) -> Int64 Calculates n! (factorial). Returns 1 for n ≤ 1.
Math::randomSeed fn randomSeed(seed: Int64) -> Int64 LCG-based pseudo-random generator. The same seed always produces the same value.

Example

import tinox.core.math;

class Main
{
    fnc main() -> Int32
    {
        // Konstanten ausgeben
        let pi: Float64 = Math::pi();
        let e: Float64  = Math::e();
        println("Pi = " + pi.toString());
        println("e  = " + e.toString());

        // Grundoperationen
        let a: Int64 = -7;
        let b: Int64 = 3;
        println("abs(-7)    = " + Math::abs(a).toString());
        println("min(-7, 3) = " + Math::min(a, b).toString());
        println("max(-7, 3) = " + Math::max(a, b).toString());
        println("square(5)  = " + Math::square(5).toString());

        // Power and factorial
        println("2^10       = " + Math::pow(2, 10).toString());
        println("7!         = " + Math::factorial(7).toString());

        // Pseudo-random
        let rnd: Int64 = Math::randomSeed(42);
        println("randomSeed(42) = " + rnd.toString());
        return 0;
    }
}

mathf

import tinox.core.mathf;

The mathf module extends basic math with floating-point operations. It provides square root, power, absolute value, rounding (floor/ceil/round), clamp, linear interpolation, and trigonometric and logarithmic functions. All inputs and outputs are based on Float64.

Classes & Methods

Class / MethodSignatureDescription
Mathf::pi fn pi() -> Float64 Returns π.
Mathf::e fn e() -> Float64 Returns Euler's number e.
Mathf::sqrt fn sqrt(x: Float64) -> Float64 Calculates the square root using the Heron method. Throws an error for negative input.
Mathf::pow fn pow(base: Float64, exp: Float64) -> Float64 Calculates base to the power of exp (integer exponent).
Mathf::abs fn abs(x: Float64) -> Float64 Returns the absolute value of a floating-point number.
Mathf::floor fn floor(x: Float64) -> Int64 Rounds to the nearest integer toward –∞ (floor).
Mathf::ceil fn ceil(x: Float64) -> Int64 Rounds to the nearest integer toward +∞ (ceiling).
Mathf::round fn round(x: Float64) -> Int64 Commercial rounding: rounds up from 0.5.
Mathf::min fn min(a: Float64, b: Float64) -> Float64 Returns the smaller of two float values.
Mathf::max fn max(a: Float64, b: Float64) -> Float64 Returns the larger of two float values.
Mathf::clamp fn clamp(value: Float64, min: Float64, max: Float64) -> Float64 Clamps value to the interval [min, max].
Mathf::lerp fn lerp(a: Float64, b: Float64, t: Float64) -> Float64 Linear interpolation between a and b with factor t ∈ [0, 1].
Mathf::sin fn sin(x: Float64) -> Float64 Sine (radians), delegates to sinf.
Mathf::cos fn cos(x: Float64) -> Float64 Cosine (radians), delegates to cosf.
Mathf::tan fn tan(x: Float64) -> Float64 Tangent (radians), delegates to tanf.
Mathf::log fn log(x: Float64) -> Float64 Natural logarithm (base e), delegates to logf.
Mathf::log10 fn log10(x: Float64) -> Float64 Decimal logarithm (base 10), delegates to log10f.

Example

import tinox.core.mathf;

class Main
{
    fnc main() -> Int32
    {
        // Wurzel und Rundung
        let r: Float64 = Mathf::sqrt(2.0);
        println("sqrt(2) = " + r.toString());
        println("floor(2.9)  = " + Mathf::floor(2.9).toString());
        println("ceil(2.1)   = " + Mathf::ceil(2.1).toString());
        println("round(2.5)  = " + Mathf::round(2.5).toString());

        // Clamp und Lerp
        let clamped: Float64 = Mathf::clamp(150.0, 0.0, 100.0);
        println("clamp(150, 0, 100) = " + clamped.toString());
        let mid: Float64 = Mathf::lerp(0.0, 10.0, 0.5);
        println("lerp(0, 10, 0.5) = " + mid.toString());

        // Trigonometrie
        let angle: Float64 = Mathf::pi() / 4.0;
        println("sin(pi/4) = " + Mathf::sin(angle).toString());
        println("cos(pi/4) = " + Mathf::cos(angle).toString());

        // Logarithmen
        println("log(e)   = " + Mathf::log(Mathf::e()).toString());
        println("log10(100) = " + Mathf::log10(100.0).toString());
        return 0;
    }
}

mathx

import tinox.core.mathx;

The mathx module provides extended number-theoretic and combinatorial functions: GCD, LCM, primality test, next prime, Fibonacci sequence, binomial coefficient, permutations, modular exponentiation, and digit sum and digit count.

Classes & Methods

Class / MethodSignatureDescription
Mathx::gcd fn gcd(a: Int64, b: Int64) -> Int64 Calculates the greatest common divisor (Euclidean algorithm).
Mathx::lcm fn lcm(a: Int64, b: Int64) -> Int64 Calculates the least common multiple via |a · b| / gcd(a, b).
Mathx::isPrime fn isPrime(n: Int64) -> Bool Returns true if n is a prime number.
Mathx::nextPrime fn nextPrime(n: Int64) -> Int64 Returns the smallest prime ≥ n.
Mathx::fibonacci fn fibonacci(n: Int64) -> Int64 Calculates the nth Fibonacci number iteratively (F(0)=0, F(1)=1).
Mathx::combinations fn combinations(n: Int64, k: Int64) -> Int64 Calculates the binomial coefficient C(n, k) = n! / (k! · (n−k)!).
Mathx::permutations fn permutations(n: Int64, k: Int64) -> Int64 Calculates the number of k-permutations of n elements: n! / (n−k)!.
Mathx::factorial fn factorial(n: Int64) -> Int64 Internal helper function for factorial, also directly usable.
Mathx::modPow fn modPow(base: Int64, exp: Int64, mod: Int64) -> Int64 Fast modular exponentiation via repeated squaring: (base^exp) % mod.
Mathx::digitSum fn digitSum(n: Int64) -> Int64 Calculates the digit sum (sum of all decimal digits) of n.
Mathx::digitCount fn digitCount(n: Int64) -> Int64 Counts the number of decimal digits of n (special case: 0 yields 1).

Example

import tinox.core.mathx;

class Main
{
    fnc main() -> Int32
    {
        // ggT und kgV
        println("gcd(48, 18) = " + Mathx::gcd(48, 18).toString());
        println("lcm(4, 6)   = " + Mathx::lcm(4, 6).toString());

        // Primzahlen
        println("isPrime(17) = " + Mathx::isPrime(17).toString());
        println("isPrime(18) = " + Mathx::isPrime(18).toString());
        println("nextPrime(20) = " + Mathx::nextPrime(20).toString());

        // Fibonacci
        println("fibonacci(10) = " + Mathx::fibonacci(10).toString());

        // Kombinatorik
        println("C(10, 3) = " + Mathx::combinations(10, 3).toString());
        println("P(5, 2)  = " + Mathx::permutations(5, 2).toString());

        // Modulare Potenz und Ziffernanalyse
        println("modPow(2, 10, 1000) = " + Mathx::modPow(2, 10, 1000).toString());
        println("digitSum(12345)     = " + Mathx::digitSum(12345).toString());
        println("digitCount(99999)   = " + Mathx::digitCount(99999).toString());
        return 0;
    }
}

complex

import tinox.core.complex;

The complex module provides complex numbers as immutable value types. It supports creation from Cartesian and polar coordinates, all four basic arithmetic operations, magnitude, phase, conjugation, reciprocal, and the complex functions exp, log, and pow. Output is as a readable string in the form a±bi.

Classes & Methods

Class / MethodSignatureDescription
Complex::new fn new(real: Float64, imag: Float64) -> Complex Creates a complex number from real and imaginary parts.
Complex::fromPolar fn fromPolar(r: Float64, theta: Float64) -> Complex Creates a complex number from polar coordinates (magnitude r, angle theta in radians).
Complex::add fn add(a: Complex, b: Complex) -> Complex Addition of two complex numbers.
Complex::subtract fn subtract(a: Complex, b: Complex) -> Complex Subtraction of two complex numbers.
Complex::multiply fn multiply(a: Complex, b: Complex) -> Complex Multiplication of two complex numbers using the formula (ac−bd) + (ad+bc)i.
Complex::divide fn divide(a: Complex, b: Complex) -> Complex Division a / b via conjugation of the denominator.
Complex::magnitude fn magnitude(c: Complex) -> Float64 Calculates the magnitude |c| = sqrt(re² + im²).
Complex::phase fn phase(c: Complex) -> Float64 Calculates the phase angle (argument) via atan2(im, re).
Complex::conjugate fn conjugate(c: Complex) -> Complex Returns the complex conjugate (sign of imaginary part reversed).
Complex::reciprocal fn reciprocal(c: Complex) -> Complex Calculates the reciprocal 1 / c.
Complex::toString fn toString(c: Complex) -> String Formats the complex number as a readable string, e.g. 3.0+4.0i.
Complex::exp fn exp(c: Complex) -> Complex Calculates e^c = e^re · (cos(im) + i · sin(im)).
Complex::log fn log(c: Complex) -> Complex Principal value of the natural logarithm: ln(|c|) + i · arg(c).
Complex::pow fn pow(c: Complex, n: Float64) -> Complex Calculates c^n in polar form: |c|^n · e^(i·n·arg(c)).

Example

import tinox.core.complex;

class Main
{
    fnc main() -> Int32
    {
        // Erzeugung
        let a: Complex = Complex::new(3.0, 4.0);
        let b: Complex = Complex::new(1.0, -2.0);

        // Grundrechenarten
        println("a + b = " + Complex::toString(Complex::add(a, b)));
        println("a - b = " + Complex::toString(Complex::subtract(a, b)));
        println("a * b = " + Complex::toString(Complex::multiply(a, b)));
        println("a / b = " + Complex::toString(Complex::divide(a, b)));

        // Betrag und Phase
        println("|a| = " + Complex::magnitude(a).toString());
        println("arg(a) = " + Complex::phase(a).toString());

        // Conjugation and reciprocal
        println("conj(a)      = " + Complex::toString(Complex::conjugate(a)));
        println("reciprocal(b)= " + Complex::toString(Complex::reciprocal(b)));

        // Polar-Erzeugung und Potenz
        let c: Complex = Complex::fromPolar(1.0, Mathf::pi() / 2.0);
        println("fromPolar(1, pi/2) = " + Complex::toString(c));
        println("a^2 = " + Complex::toString(Complex::pow(a, 2.0)));
        return 0;
    }
}

decimal

import tinox.core.decimal;

The decimal module implements fixed-point decimal numbers with arbitrary precision based on strings. It enables lossless addition, subtraction, and multiplication without floating-point rounding errors. Division is available with selectable precision (decimal places). Useful for financial and accounting applications.

Classes & Methods

Class / MethodSignatureDescription
Decimal::new fn new(value: String) -> Decimal Creates a decimal number from a string literal.
Decimal::fromInt fn fromInt(value: Int64) -> Decimal Creates a decimal number from an integer value (scale = 0).
Decimal::fromFloat fn fromFloat(value: Float64) -> Decimal Creates a decimal number from a Float64 value with scale = 10.
Decimal::add fn add(a: Decimal, b: Decimal) -> Decimal Adds two decimal numbers with automatic scale alignment.
Decimal::subtract fn subtract(a: Decimal, b: Decimal) -> Decimal Subtracts b from a with automatic scale alignment.
Decimal::multiply fn multiply(a: Decimal, b: Decimal) -> Decimal Multiplies two decimal numbers; the total scale is the sum of the individual scales.
Decimal::divide fn divide(a: Decimal, b: Decimal, precision: Int64) -> Decimal Divides a by b with the specified decimal place precision.
Decimal::scaleValue fn scaleValue(value: String, digits: Int64) -> String Internal helper method: appends digits zeros to a digit sequence.
Decimal::addStrings fn addStrings(a: String, b: String) -> String Adds two integers represented as strings.
Decimal::subtractStrings fn subtractStrings(a: String, b: String) -> String Subtracts two integers represented as strings.
Decimal::multiplyStrings fn multiplyStrings(a: String, b: String) -> String Multiplies two integers represented as strings.
Decimal::toString fn toString(d: Decimal) -> String Converts a decimal number into its readable string representation with decimal point.

Example

import tinox.core.decimal;

class Main
{
    fnc main() -> Int32
    {
        // Create from different sources
        let price:   Decimal = Decimal::new("1999");
        let tax:     Decimal = Decimal::fromInt(319);
        let rate:    Decimal = Decimal::fromFloat(0.035);

        // Basic arithmetic
        let total: Decimal = Decimal::add(price, tax);
        println("Price + Tax = " + Decimal::toString(total));

        let diff: Decimal = Decimal::subtract(price, tax);
        println("Difference = " + Decimal::toString(diff));

        let qty:    Decimal = Decimal::fromInt(3);
        let product: Decimal = Decimal::multiply(price, qty);
        println("3 x Price = " + Decimal::toString(product));

        // Division with precision
        let result: Decimal = Decimal::divide(price, qty, 4);
        println("Price / 3 (4 digits) = " + Decimal::toString(result));
        return 0;
    }
}

random

import tinox.core.random;

The random module provides a convenient interface for generating random values. It provides methods for random integers, floating-point numbers, and booleans, and allows random selection of an element, shuffling, and drawing a random sample from a list. Additionally, a random alphanumeric string can be generated.

Classes & Methods

Class / MethodSignatureDescription
Random::nextInt fn nextInt(max: Int64) -> Int64 Returns a random integer in the range [0, max).
Random::nextFloat fn nextFloat() -> Float64 Returns a random floating-point number in the range [0.0, 1.0).
Random::nextBool fn nextBool() -> Bool Randomly returns true or false (50% probability).
Random::pickOne<T> fn pickOne<T>(list: List<T>) -> T Randomly selects an element from the provided list.
Random::shuffle<T> fn shuffle<T>(list: List<T>) -> List<T> Shuffles the list using the Fisher-Yates algorithm and returns the shuffled copy.
Random::sample<T> fn sample<T>(list: List<T>, count: Int64) -> List<T> Draws a random sample of count elements without replacement.
Random::generateString fn generateString(length: Int64) -> String Generates a random alphanumeric string of the specified length.

Example

import tinox.core.random;

class Main
{
    fnc main() -> Int32
    {
        // Simple random values
        println("nextInt(100)  = " + Random::nextInt(100).toString());
        println("nextFloat()   = " + Random::nextFloat().toString());
        println("nextBool()    = " + Random::nextBool().toString());

        // Pick from a list
        let colors: List = ["red", "green", "blue", "yellow"];
        println("pickOne = " + Random::pickOne(colors));

        // Shuffle
        let nums: List = [1, 2, 3, 4, 5, 6, 7, 8];
        let shuffled: List = Random::shuffle(nums);
        println("shuffle([1..8]) has " + shuffled.len().toString() + " elements");

        // Sample
        let sample: List = Random::sample(colors, 2);
        println("sample(colors, 2) = " + sample.join(", "));

        // Random string
        let token: String = Random::generateString(16);
        println("Token = " + token);
        return 0;
    }
}

string

import tinox.core.string;

The string module provides the Strings class that bundles frequently needed string operations as static methods: length query, uppercase/lowercase, search, extraction, transformation, splitting, joining, and conversion functions from numbers to strings.

Classes & Methods

Class / MethodSignatureDescription
Strings::length fn length(s: String) -> Int64 Returns the number of characters in the string.
Strings::isEmpty fn isEmpty(s: String) -> Bool Returns true if the string is empty (length = 0).
Strings::toUpperCase fn toUpperCase(s: String) -> String Converts all characters to uppercase.
Strings::toLowerCase fn toLowerCase(s: String) -> String Converts all characters to lowercase.
Strings::trim fn trim(s: String) -> String Removes leading and trailing whitespace.
Strings::contains fn contains(haystack: String, needle: String) -> Bool Returns true if needle occurs in haystack.
Strings::startsWith fn startsWith(s: String, prefix: String) -> Bool Checks whether the string starts with the specified prefix.
Strings::endsWith fn endsWith(s: String, suffix: String) -> Bool Checks whether the string ends with the specified suffix.
Strings::substring fn substring(s: String, start: Int64, end: Int64) -> String Extracts the substring from index start (incl.) to end (excl.).
Strings::repeat fn repeat(s: String, count: Int64) -> String Repeats the string count times and returns the concatenated string.
Strings::replace fn replace(s: String, old: String, repl: String) -> String Replaces all occurrences of old with repl.
Strings::reverse fn reverse(s: String) -> String Reverses the character order of the string.
Strings::split fn split(s: String, delimiter: String) -> List<String> Splits the string at the specified delimiter and returns a list.
Strings::join fn join(parts: List<String>, separator: String) -> String Joins a list of strings with the specified delimiter.
Strings::intToString fn intToString(n: Int64) -> String Converts an integer into its string representation.
Strings::floatToString fn floatToString(f: Float64) -> String Converts a floating-point number into its string representation.

Example

import tinox.core.string;

class Main
{
    fnc main() -> Int32
    {
        let text: String = "  Hello, Tinox-World!  ";

        // Basic operations
        println("Length (trimmed): " + Strings::length(Strings::trim(text)).toString());
        println("Uppercase:        " + Strings::toUpperCase(Strings::trim(text)));
        println("Lowercase:        " + Strings::toLowerCase(Strings::trim(text)));

        // Search and check
        println("Contains 'Tinox': " + Strings::contains(text, "Tinox").toString());
        println("Starts with '  H': " + Strings::startsWith(text, "  H").toString());
        println("Ends with '!  ':   " + Strings::endsWith(text, "!  ").toString());

        // Transformation
        println("Reverse:   " + Strings::reverse("Tinox"));
        println("Repeat:    " + Strings::repeat("ab", 4));
        println("Replace:   " + Strings::replace(text, "Tinox", "World"));

        // Split and join
        let parts: List = Strings::split("a,b,c,d", ",");
        println("Parts: " + Strings::join(parts, " | "));

        // Number conversion
        println(Strings::intToString(42) + " / " + Strings::floatToString(3.14));
        return 0;
    }
}

fmt

import tinox.core.fmt;

The fmt module provides printf-like formatting for strings. The Fmt class supports the placeholders %s (string), %d (integer), %f (float), %x (hexadecimal), and %b (binary), as well as %% for a literal percent sign. Output can be produced both as a string and printed directly to the console.

Classes & Methods

Class / MethodSignatureDescription
Fmt::sprintf fn sprintf(format: String, args: List<String>) -> String Formats a string using the format pattern and argument list. Supported placeholders: %s, %d, %f, %x (hex), %b (binary), %% (literal %).
Fmt::printf fn printf(format: String, args: List<String>) -> Nothing Prints the formatted string without a trailing newline to the console.
Fmt::printlnf fn printlnf(format: String, args: List<String>) -> Nothing Prints the formatted string with a trailing newline to the console.

Example

import tinox.core.fmt;

class Main
{
    fnc main() -> Int32
    {
        // Simple string interpolation
        let name: String = Fmt::sprintf("Hello, %s!", ["Tinox"]);
        println(name);

        // Mixed types in one line
        Fmt::printlnf("User: %s | Age: %d | Balance: %f EUR",
                      ["Alice", "30", "123.45"]);

        // Hexadecimal and binary output
        Fmt::printlnf("255 in hex: %x", ["255"]);
        Fmt::printlnf("255 in bin: %b", ["255"]);

        // Literal percent sign
        Fmt::printlnf("Discount: 20%%", []);

        // Reuse sprintf return value
        let line: String = Fmt::sprintf("[%s] Error %d", ["WARN", "404"]);
        println(line);
        return 0;
    }
}

format

import tinox.core.format;

The format module provides helper functions for displaying and aligning values. It enables right-aligned and left-aligned padding of strings to a minimum width, conversion of integers to binary and hexadecimal representations, and formatting of an integer with fixed field width.

Classes & Methods

Class / MethodSignatureDescription
Format::padLeft fn padLeft(s: String, width: Int64, char: String) -> String Pads the string from the left with char until the total width width is reached (right-aligned).
Format::padRight fn padRight(s: String, width: Int64, char: String) -> String Pads the string from the right with char until the total width width is reached (left-aligned).
Format::intToBinary fn intToBinary(n: Int64) -> String Converts a non-negative integer to its binary representation (e.g. 10"1010").
Format::intToHex fn intToHex(n: Int64) -> String Converts a non-negative integer to its hexadecimal representation (lowercase).
Format::formatInt fn formatInt(n: Int64, width: Int64) -> String Returns n right-aligned in a field of width width (space as fill character).

Example

import tinox.core.format;

class Main
{
    fnc main() -> Int32
    {
        // Padding
        println(Format::padLeft("42", 6, " "));    // "    42"
        println(Format::padLeft("42", 6, "0"));    // "000042"
        println(Format::padRight("OK", 8, "."));   // "OK......"

        // Number bases
        println("255 binary: "  + Format::intToBinary(255));   // "11111111"
        println("255 hex:   "  + Format::intToHex(255));      // "ff"
        println("4096 hex:  "  + Format::intToHex(4096));     // "1000"

        // Tabular output
        var i: Int64 = 1;
        while i <= 5
        {
            println(Format::formatInt(i, 4) + " | " + Format::formatInt(i * i, 6));
            i = i + 1;
        }
        return 0;
    }
}

regex

import tinox.core.regex;

The regex module provides regular expressions for Tinox. The Regex class offers methods for checking matches, finding the first or all occurrences, globally replacing, and splitting text by a pattern. Execution delegates to native runtime functions (regexIsMatch, regexFindFirst, etc.).

Classes & Methods

Class / MethodSignatureDescription
Regex::isMatch fn isMatch(pattern: String, text: String) -> Bool Returns true if text matches the regular expression pattern.
Regex::findFirst fn findFirst(pattern: String, text: String) -> String Returns the first match of the pattern in the text; empty string if no match.
Regex::findAll fn findAll(pattern: String, text: String) -> List<String> Returns all non-overlapping matches of the pattern as a list.
Regex::replaceAll fn replaceAll(pattern: String, text: String, replacement: String) -> String Replaces all matches of the pattern in the text with replacement.
Regex::split fn split(pattern: String, text: String) -> List<String> Splits text at all positions matching the pattern and returns the parts as a list.

Example

import tinox.core.regex;

class Main
{
    fnc main() -> Int32
    {
        let text: String = "Order no: 1042, Qty: 3, Price: 29.99 EUR";

        // Match check
        let hasNumber: Bool = Regex::isMatch("[0-9]+", text);
        println("Contains numbers: " + hasNumber.toString());

        // Find first match
        let firstNumber: String = Regex::findFirst("[0-9]+", text);
        println("First number: " + firstNumber);

        // Find all matches
        let allNumbers: List = Regex::findAll("[0-9]+\\.?[0-9]*", text);
        println("All numbers: " + allNumbers.join(", "));

        // Replace
        let sanitized: String = Regex::replaceAll("[0-9]+", text, "###");
        println("Anonymized: " + sanitized);

        // Split
        let csv: String = "alpha;  beta;gamma; delta";
        let parts: List = Regex::split(";\\s*", csv);
        println("Parts: " + parts.join(", "));
        return 0;
    }
}

uri

import tinox.core.uri;

The uri module provides functions for working with URIs and URLs. It allows encoding and decoding of URIs (fully or component-wise) as well as decomposing a URL into its parts: scheme, host, path, query string, and fragment. The parser result is returned as a UriParts structure.

Classes & Methods

Class / MethodSignatureDescription
Uri::encode fn encode(s: String) -> String Encodes a complete URI (reserved characters like /, ?, # are preserved).
Uri::decode fn decode(s: String) -> String Decodes a URI-encoded string.
Uri::encodeComponent fn encodeComponent(s: String) -> String Encodes a URI component (reserved characters are also percent-encoded).
Uri::decodeComponent fn decodeComponent(s: String) -> String Decodes a percent-encoded URI component.
Uri::parse fn parse(url: String) -> UriParts Decomposes a URL into its parts. Returns a UriParts structure with the fields scheme, host, path, query, and fragment. If the :// sequence is missing, all fields are empty.
UriParts struct { scheme, host, path, query, fragment: String } Data structure with the five parts of a decomposed URL.

Example

import tinox.core.uri;

class Main
{
    fnc main() -> Int32
    {
        // Encoding and decoding
        let raw: String = "Search term: Daily fresh!";
        let encoded: String = Uri::encodeComponent(raw);
        println("Encoded:   " + encoded);
        println("Decoded:   " + Uri::decodeComponent(encoded));

        // Encode full URI
        let url: String = "https://example.com/search?q=Tinox Docs&lang=en";
        println("URI encoded: " + Uri::encode(url));

        // Parse URL
        let complex: String = "https://api.tinox.dev:8080/v1/users?limit=10&page=2#results";
        let parts: UriParts = Uri::parse(complex);
        println("Scheme:   " + parts.scheme);
        println("Host:     " + parts.host);
        println("Path:     " + parts.path);
        println("Query:    " + parts.query);
        println("Fragment: " + parts.fragment);

        // Simple URL without path
        let simple: UriParts = Uri::parse("ftp://files.example.org");
        println("FTP host: " + simple.host);
        return 0;
    }
}

fs

import tinox.core.fs;

The fs module provides basic filesystem operations. It covers reading, writing, copying, moving, and deleting files as well as creating, listing, and removing directories.

Classes & Methods

Class / MethodSignatureDescription
Fs::readFile fn readFile(path: String) -> String Reads the entire content of the file at path and returns it as a string.
Fs::writeFile fn writeFile(path: String, content: String) -> Nothing Writes content to the file at path (overwrites existing contents).
Fs::appendFile fn appendFile(path: String, content: String) -> Nothing Appends content to the existing content of the file.
Fs::deleteFile fn deleteFile(path: String) -> Nothing Deletes the file at path from the filesystem.
Fs::copyFile fn copyFile(src: String, dest: String) -> Nothing Copies the file from src to dest.
Fs::moveFile fn moveFile(src: String, dest: String) -> Nothing Moves the file from src to dest (copy + delete).
Fs::listDirectory fn listDirectory(path: String) -> List<String> Returns a list of all entries (files and subdirectories) in the specified directory.
Fs::createDirectory fn createDirectory(path: String) -> Nothing Creates a new directory at path.
Fs::deleteDirectory fn deleteDirectory(path: String) -> Nothing Removes the directory at path (must be empty).

Example

import tinox.core.fs;

// Create directory and write files
Fs::createDirectory("/tmp/demo");

Fs::writeFile("/tmp/demo/hello.txt", "Hello, Tinox!\n");
Fs::appendFile("/tmp/demo/hello.txt", "Second line.\n");

// Read file and print
let content: String = Fs::readFile("/tmp/demo/hello.txt");
print(content);

// Copy file and delete original (= move)
Fs::copyFile("/tmp/demo/hello.txt", "/tmp/demo/backup.txt");
Fs::deleteFile("/tmp/demo/hello.txt");

// List directory contents
let entries: List<String> = Fs::listDirectory("/tmp/demo");
var i: Int64 = 0;
while i < entries.len()
{
    print(entries[i] + "\n");
    i = i + 1;
}

// Clean up
Fs::deleteFile("/tmp/demo/backup.txt");
Fs::deleteDirectory("/tmp/demo");

io

import tinox.core.io;

The io module provides I/O utilities: The Io class offers console output, File represents a file handle for opening, reading, writing, and closing, Paths enables decomposition and composition of file paths, and Buffer is used for efficient string accumulation.

Classes & Methods

Class / MethodSignatureDescription
Io::newBuffer fn newBuffer() -> Buffer Creates a new, empty Buffer.
Io::printLine fn printLine(value: String) -> Nothing Prints a string followed by a newline to the console.
Io::printHeader fn printHeader(title: String) -> Nothing Prints title as a highlighted heading (e.g. === Title ===).
File::new fn new(path: String) -> File Creates a new, not yet opened File instance for the specified path.
File::getPath fn getPath() -> String Returns the file path of the instance.
File::close fn close() -> Nothing Closes the file if it is open and resets the handle.
File::exists fn exists(path: String) -> Bool Checks whether a file exists at path.
Paths::getExtension fn getExtension(filename: String) -> String Extracts the file extension (without dot) from a filename, e.g. "txt".
Paths::getFileName fn getFileName(filePath: String) -> String Extracts the filename from a full path, e.g. "file.txt".
Paths::getDirectory fn getDirectory(filePath: String) -> String Returns the directory part of a path, e.g. "/path/to".
Paths::join fn join(dir: String, file: String) -> String Joins directory and filename with a slash.
Buffer::new fn new() -> Buffer Creates a new, empty buffer.
Buffer::append fn append(s: String) -> Nothing Appends a string to the buffer.
Buffer::appendInt fn appendInt(n: Int64) -> Nothing Appends the text representation of an integer to the buffer.
Buffer::appendLine fn appendLine(s: String) -> Nothing Appends a string with a trailing newline.
Buffer::clear fn clear() -> Nothing Clears all buffer content.
Buffer::toString fn toString() -> String Returns the accumulated buffer content as a string.
Buffer::isEmpty fn isEmpty() -> Bool Returns true if the buffer has no content.

Example

import tinox.core.io;

// Use path helper functions
let path: String = "/home/user/documents/report.txt";
Io::printLine("Filename  : " + Paths::getFileName(path));
Io::printLine("Extension : " + Paths::getExtension(path));
Io::printLine("Directory : " + Paths::getDirectory(path));

let newPath: String = Paths::join("/home/user/export", "output.csv");
Io::printLine("New path: " + newPath);

// Buffer for efficient concatenation
let buf: Buffer = Io::newBuffer();
buf.appendLine("Line 1");
buf.appendLine("Line 2");
buf.append("Number: ");
buf.appendInt(42);

if !buf.isEmpty()
{
    Io::printHeader("Buffer contents");
    Io::printLine(buf.toString());
}

// Check file handle
let file: File = File::new(path);
if File::exists(path)
{
    Io::printLine("File exists: " + file.getPath());
    file.close();
}

env

import tinox.core.env;

The env module provides access to environment variables of the running process as well as querying and changing the current working directory.

Classes & Methods

Class / MethodSignatureDescription
Env::getVar fn getVar(name: String) -> String Returns the value of the environment variable name.
Env::setVar fn setVar(name: String, value: String) -> Nothing Sets the environment variable name to value.
Env::removeVar fn removeVar(name: String) -> Nothing Removes the environment variable name.
Env::currentDir fn currentDir() -> String Returns the current working directory of the process.
Env::setCurrentDir fn setCurrentDir(path: String) -> Nothing Changes the working directory to path.

Example

import tinox.core.env;

// Read environment variable
let home: String = Env::getVar("HOME");
print("Home directory: " + home + "\n");

// Set and read new variable
Env::setVar("APP_MODE", "production");
let mode: String = Env::getVar("APP_MODE");
print("Mode: " + mode + "\n");

// Get and change working directory
let cwd: String = Env::currentDir();
print("Current directory: " + cwd + "\n");

Env::setCurrentDir("/tmp");
print("New directory: " + Env::currentDir() + "\n");

// Remove variable again
Env::removeVar("APP_MODE");
print("APP_MODE removed.\n");

process

import tinox.core.process;

The process module provides control functions for the running OS process: exiting with an exit code, sleeping, querying the process ID and command-line arguments, and accessing environment variables.

Classes & Methods

Class / MethodSignatureDescription
Process::exit fn exit(code: Int64) -> Nothing Terminates the process with the specified exit code.
Process::sleep fn sleep(ms: Int64) -> Nothing Pauses execution for ms milliseconds.
Process::pid fn pid() -> Int64 Returns the process ID (PID) of the running process.
Process::args fn args() -> List<String> Returns the command-line arguments as a list.
Process::env fn env(name: String) -> String Reads the value of the environment variable name.
Process::setEnv fn setEnv(name: String, value: String) -> Nothing Sets the environment variable name to value.

Example

import tinox.core.process;

// Print PID and arguments
let pid: Int64 = Process::pid();
print("Process ID: " + pid.toString() + "\n");

let args: List<String> = Process::args();
print("Argument count: " + args.len().toString() + "\n");

var i: Int64 = 0;
while i < args.len()
{
    print("  arg[" + i.toString() + "] = " + args[i] + "\n");
    i = i + 1;
}

// Set and read environment variable
Process::setEnv("TINOX_LOG", "debug");
let logLevel: String = Process::env("TINOX_LOG");
print("Log level: " + logLevel + "\n");

// Brief pause, then clean exit
Process::sleep(500);
print("Program end.\n");
Process::exit(0);

debug

import tinox.core.debug;

The debug module provides tools for runtime diagnostics: assertions, controlled program abort, stack trace output, memory usage query, and manual garbage collection.

Classes & Methods

Class / MethodSignatureDescription
Debug::assert fn assert(condition: Bool, message: String) -> Nothing Throws an exception with message when condition is false.
Debug::panic fn panic(message: String) -> Nothing Immediately aborts the program with the message "Assertion failed: " + message.
Debug::printStackTrace fn printStackTrace() -> Nothing Prints the current call stack to the console.
Debug::typeName fn typeName() -> String Returns the type name of the current object (default "unknown").
Debug::memoryUsage fn memoryUsage() -> Int64 Returns the current memory usage of the runtime environment in bytes.
Debug::gcCollect fn gcCollect() -> Nothing Manually triggers a garbage collection run.

Example

import tinox.core.debug;

// Measure memory usage before work
let before: Int64 = Debug::memoryUsage();
print("Memory before: " + before.toString() + " bytes\n");

// Assertion – fails if condition not met
let value: Int64 = 42;
Debug::assert(value > 0, "Value must be positive");
Debug::assert(value < 100, "Value must not exceed 100");

// Print stack trace for diagnostic purposes
Debug::printStackTrace();

// Force garbage collection and measure memory again
Debug::gcCollect();
let after: Int64 = Debug::memoryUsage();
print("Memory after: " + after.toString() + " bytes\n");

// Intentional abort on unexpected state
let error: Bool = false;
if error
{
    Debug::panic("Unexpected system state");
}

print("All checks passed.\n");

zip

import tinox.core.zip;

The zip module enables creating, opening, and editing ZIP archives. Files can be added, extracted, and removed. Entries are described via ZipEntry objects with name and size; the archive itself is represented by ZipArchive.

Classes & Methods

Class / MethodSignatureDescription
Zip::open fn open(path: String) -> ZipArchive Opens an existing ZIP archive and reads its entries.
Zip::create fn create(path: String) -> ZipArchive Creates a new, empty ZIP archive at the specified path.
Zip::listEntries fn listEntries(path: String) -> List<ZipEntry> Returns all entries of an archive as a list of ZipEntry objects.
Zip::extractFile fn extractFile(archive: ZipArchive, entryName: String) -> String Extracts the content of an archive entry as a string.
Zip::addFile fn addFile(archive: ZipArchive, name: String, content: String) -> Nothing Adds a new file with the specified name and content to the archive.
Zip::removeFile fn removeFile(archive: ZipArchive, entryName: String) -> Nothing Removes an entry from the archive.
Zip::close fn close(archive: ZipArchive) -> Nothing Closes the archive (final operation after editing).
ZipArchive path: String; entries: List<ZipEntry> Data structure: represents a ZIP archive with path and entry list.
ZipEntry name: String; size: Int64 Data structure: describes a single archive entry with name and size in bytes.

Example

import tinox.core.zip;

// Create new archive and add files
let archive: ZipArchive = Zip::create("/tmp/package.zip");
Zip::addFile(archive, "README.txt",  "This is a Tinox demo.\n");
Zip::addFile(archive, "data.csv",    "name,value\nalpha,1\nbeta,2\n");
Zip::addFile(archive, "config.json", "{\"version\": 1}\n");

// List entries
let entries: List<ZipEntry> = archive.entries;
var i: Int64 = 0;
while i < entries.len()
{
    let e: ZipEntry = entries[i];
    print(e.name + "  (" + e.size.toString() + " bytes)\n");
    i = i + 1;
}

// Extract content of a file
let content: String = Zip::extractFile(archive, "README.txt");
print("README: " + content);

// Remove entry
Zip::removeFile(archive, "config.json");
print("Entries after deletion: " + archive.entries.len().toString() + "\n");

Zip::close(archive);

compress

import tinox.core.compress;

The compress module provides general-purpose byte compression for arbitrary payloads (log shipping, HTTP response bodies, message-queue payload compression, ...) — unlike zip's Zip, which is STORED-only (no compression). Two formats are supported: gzip (RFC 1952 — magic bytes, header, deflate stream, then a CRC32 + size trailer that decompression verifies automatically; the format most external tools/HTTP Content-Encoding: gzip expect) and raw DEFLATE (RFC 1951, no header/trailer — smallest possible framing, the same on-wire format the websocket module's permessage-deflate already uses). Prefer gzip/gunzip unless framing size specifically matters and both ends already agree out-of-band on the format.

Decompression failure (truncated/malformed input, a CRC32/trailer mismatch, or a payload exceeding a 16MB decompression-bomb cap) is reported via lastGunzipOk()/lastInflateOk() rather than a silent empty-but-"successful" result — check it immediately after calling gunzip()/inflateRaw().

Classes & Methods

Class / MethodSignatureDescription
Compress::gzip fnc gzip(bytes: List<Int64>) -> List<Int64> Compresses bytes into a gzip (RFC 1952) container.
Compress::gunzip fnc gunzip(bytes: List<Int64>) -> List<Int64> Decompresses a gzip container back to the original bytes. On failure, returns an empty list and lastGunzipOk() reports false.
Compress::lastGunzipOk fnc lastGunzipOk() -> Bool Whether the most recent gunzip() call on this thread succeeded. Check immediately after calling gunzip().
Compress::deflateRaw fnc deflateRaw(bytes: List<Int64>) -> List<Int64> Compresses bytes with raw DEFLATE (RFC 1951, no gzip header/trailer).
Compress::inflateRaw fnc inflateRaw(bytes: List<Int64>) -> List<Int64> Decompresses a raw DEFLATE stream. On failure, returns an empty list and lastInflateOk() reports false.
Compress::lastInflateOk fnc lastInflateOk() -> Bool Whether the most recent inflateRaw() call on this thread succeeded. Check immediately after calling inflateRaw().

Example

import tinox.core.compress;

let msg: String = "hello, tinox compress module!";
var bytes: List<Int64> = [];
var i: Int64 = 0;
while i < msg.len()
{
    bytes.push(msg.charCodeAt(i));
    i = i + 1;
}

// gzip round trip
let packed: List<Int64> = Compress::gzip(bytes);
let restored: List<Int64> = Compress::gunzip(packed);
if !Compress::lastGunzipOk()
{
    throw "corrupt gzip stream";
}
print("compressed " + bytes.len().toString() + " bytes down to " + packed.len().toString() + "\n");

asm

import tinox.core.asm;

The asm module provides a simple bytecode assembler. Via the Assembler class, opcodes and 64-bit integer values are written into a byte buffer, labels are set and resolved, and the finished bytecode is exported as a byte string. The Ops class defines all available opcodes as integer constants.

Classes & Methods

Class / MethodSignatureDescription
Assembler::new fn new() -> Assembler Creates a new, empty assembler instance with empty bytecode and label map.
Assembler::emit fn emit(asm: Assembler, op: Int64) -> Nothing Appends a single byte (opcode) to the bytecode.
Assembler::emitInt fn emitInt(asm: Assembler, value: Int64) -> Nothing Writes a 64-bit integer value little-endian (8 bytes) into the bytecode.
Assembler::emitLabel fn emitLabel(asm: Assembler, name: String) -> Nothing Registers a label at the current bytecode position.
Assembler::resolveLabels fn resolveLabels(asm: Assembler) -> List<Int64> Returns the current bytecode (after label resolution) as an integer list.
Assembler::toBytes fn toBytes(asm: Assembler) -> String Converts the bytecode into a raw byte string (one character per byte).
Ops::Nop fn Nop() -> Int64 Opcode 0 – No operation.
Ops::Halt fn Halt() -> Int64 Opcode 1 – Halt program.
Ops::Load fn Load() -> Int64 Opcode 2 – Load value from memory.
Ops::Store fn Store() -> Int64 Opcode 3 – Store value in memory.
Ops::Push fn Push() -> Int64 Opcode 4 – Push value onto stack.
Ops::Pop fn Pop() -> Int64 Opcode 5 – Pop value from stack.
Ops::Add fn Add() -> Int64 Opcode 6 – Addition.
Ops::Sub fn Sub() -> Int64 Opcode 7 – Subtraction.
Ops::Mul fn Mul() -> Int64 Opcode 8 – Multiplication.
Ops::Div fn Div() -> Int64 Opcode 9 – Division.
Ops::Jmp fn Jmp() -> Int64 Opcode 10 – Unconditional jump.
Ops::Jz fn Jz() -> Int64 Opcode 11 – Jump if zero.
Ops::Jnz fn Jnz() -> Int64 Opcode 12 – Jump if not zero.
Ops::Cmp fn Cmp() -> Int64 Opcode 13 – Compare two values.
Ops::Call fn Call() -> Int64 Opcode 14 – Call subroutine.
Ops::Ret fn Ret() -> Int64 Opcode 15 – Return from subroutine.

Example

import tinox.core.asm;

// Create assembler
let a: Assembler = Assembler::new();

// Simple program: push 7, push 3, add, halt
Assembler::emit(a, Ops::Push());
Assembler::emitInt(a, 7);

Assembler::emit(a, Ops::Push());
Assembler::emitInt(a, 3);

Assembler::emitLabel(a, "add_start");
Assembler::emit(a, Ops::Add());

// Conditional jump back to loop (demo)
Assembler::emit(a, Ops::Jz());
Assembler::emitInt(a, 0);

Assembler::emit(a, Ops::Halt());

// Resolve labels
let code: List<Int64> = Assembler::resolveLabels(a);
print("Bytecode length: " + code.len().toString() + " bytes\n");

// Export as byte string
let bytes: String = Assembler::toBytes(a);
print("Raw data length: " + bytes.len().toString() + "\n");

bitmap

import tinox.core.bitmap;

The bitmap module provides a simple 2D pixel graphics library. Bitmaps are managed as grids of 32-bit color values (ARGB/RGB as Int64). Supported operations include creating, reading and writing pixels, filling rectangles, and drawing lines and circles (Bresenham algorithms).

Classes & Methods

Class / MethodSignatureDescription
Bitmap::create fn create(width: Int64, height: Int64, color: Int64) -> Bitmap Creates a new bitmap with the specified dimensions, all pixels set to color.
Bitmap::getPixel fn getPixel(bitmap: Bitmap, x: Int64, y: Int64) -> Int64 Returns the color value of the pixel at position (x, y); out of bounds: 0.
Bitmap::setPixel fn setPixel(bitmap: Bitmap, x: Int64, y: Int64, color: Int64) -> Nothing Sets the pixel at (x, y) to color; out of bounds: no effect.
Bitmap::fillRect fn fillRect(bitmap: Bitmap, x: Int64, y: Int64, w: Int64, h: Int64, color: Int64) -> Nothing Fills a rectangle starting at (x, y) with width w and height h in color.
Bitmap::drawLine fn drawLine(bitmap: Bitmap, x0: Int64, y0: Int64, x1: Int64, y1: Int64, color: Int64) -> Nothing Draws a line from (x0, y0) to (x1, y1) using the Bresenham algorithm.
Bitmap::drawCircle fn drawCircle(bitmap: Bitmap, cx: Int64, cy: Int64, radius: Int64, color: Int64) -> Nothing Draws a circle with center (cx, cy) and the specified radius (midpoint algorithm).

Example

import tinox.core.bitmap;

// Colors as ARGB hex values
let white: Int64  = 0xFFFFFFFF;
let black: Int64  = 0xFF000000;
let red: Int64    = 0xFFFF0000;
let blue: Int64   = 0xFF0000FF;

// Create 200×200 pixel white bitmap
let img: Bitmap = Bitmap::create(200, 200, white);

// Draw black border (four lines)
Bitmap::drawLine(img,   0,   0, 199,   0, black);
Bitmap::drawLine(img, 199,   0, 199, 199, black);
Bitmap::drawLine(img, 199, 199,   0, 199, black);
Bitmap::drawLine(img,   0, 199,   0,   0, black);

// Red filled rectangle in the center
Bitmap::fillRect(img, 50, 50, 100, 100, red);

// Blue circle over the rectangle
Bitmap::drawCircle(img, 100, 100, 40, blue);

// Read and print single pixel
let px: Int64 = Bitmap::getPixel(img, 100, 100);
print("Pixel (100,100): " + px.toString() + "\n");

// Overwrite single pixel
Bitmap::setPixel(img, 100, 100, black);
print("Pixel after overwrite: " + Bitmap::getPixel(img, 100, 100).toString() + "\n");

tpl

import tinox.core.tpl;

The tpl module provides a simple Mustache-style template engine. Templates use {{key}} placeholders that are replaced with data values. In addition to direct rendering, templates can be pre-compiled (compile) and then efficiently executed repeatedly (execute). A loop function enables rendering a template for multiple data records.

Classes & Methods

Class / MethodSignatureDescription
Template::render fn render(template: String, data: Map<String, JsonValue>) -> String Replaces all {{key}} placeholders in template with the corresponding values from data.
Template::renderLoop fn renderLoop(template: String, items: List<Map<String, JsonValue>>, itemVar: String) -> String Renders template for each element in items and concatenates the results.
Template::compile fn compile(template: String) -> CompiledTemplate Parses template into a list of TemplateSection segments (text / var / section / end).
Template::execute fn execute(tpl: CompiledTemplate, data: Map<String, JsonValue>) -> String Executes an already compiled template with the specified data.
CompiledTemplate source: String; sections: List<TemplateSection> Data structure: pre-compiled template with source text and section list.
TemplateSection kind: String; content: String Data structure: a section of the template; kind is "text", "var", "section", or "end".

Example

import tinox.core.tpl;

// Simple direct rendering
let template: String = "Hello, {{name}}! You have {{points}} points.";
let data: Map<String, JsonValue> = Map::new();
data["name"]   = JsonValue::fromString("Maria");
data["points"] = JsonValue::fromInt(1340);

let output: String = Template::render(template, data);
print(output + "\n");
// Output: Hello, Maria! You have 1340 points.

// Template loop over multiple records
let rowTemplate: String = "- {{title}} ({{year}})\n";
let movies: List<Map<String, JsonValue>> = [];

let movie1: Map<String, JsonValue> = Map::new();
movie1["title"] = JsonValue::fromString("Inception");
movie1["year"]  = JsonValue::fromInt(2010);
movies.push(movie1);

let movie2: Map<String, JsonValue> = Map::new();
movie2["title"] = JsonValue::fromString("Dune");
movie2["year"]  = JsonValue::fromInt(2021);
movies.push(movie2);

let list: String = Template::renderLoop(rowTemplate, movies, "movie");
print(list);

// Pre-compiled template for reuse
let compiled: CompiledTemplate = Template::compile("User: {{login}} | Role: {{role}}");

let admin: Map<String, JsonValue> = Map::new();
admin["login"] = JsonValue::fromString("admin");
admin["role"]  = JsonValue::fromString("Administrator");
print(Template::execute(compiled, admin) + "\n");

crypto

import tinox.core.crypto;

The crypto module provides basic cryptographic functions: hashing (MD5, SHA-256), HMAC signatures, symmetric AES encryption, secure key generation, and password-based key derivation via PBKDF2.

Classes & Methods

Class / MethodSignatureDescription
Crypto::md5fn md5(data: String) -> StringComputes the MD5 hash of the given string and returns it as a hex string.
Crypto::sha256fn sha256(data: String) -> StringComputes the SHA-256 hash of the given string and returns it as a hex string.
Crypto::hmacSha256fn hmacSha256(data: String, key: String) -> StringGenerates an HMAC-SHA-256 signature of the data with the specified key.
Crypto::aesEncryptfn aesEncrypt(data: String, key: String) -> StringEncrypts the data with AES-256-GCM (authenticated). key may be any length (derived internally into a 256-bit key via SHA-256). Returns hex-encoded nonce + ciphertext + auth tag, with a fresh random nonce per call — encrypting the same data twice yields different results. Throws on failure (e.g. runtime built without OpenSSL).
Crypto::aesDecryptfn aesDecrypt(data: String, key: String) -> StringDecrypts data produced by aesEncrypt. Throws on a wrong key or tampered/corrupted data (GCM authentication fails) instead of silently returning incorrect plaintext.
Crypto::generateKeyfn generateKey(length: Int64) -> StringGenerates a cryptographically random key of the desired byte length.
Crypto::pbkdf2fn pbkdf2(password: String, salt: String, iterations: Int64, keyLength: Int64) -> StringDerives a key from a password (PBKDF2 with SHA-256 iterations). Returns the first keyLength characters of the derived key.
Crypto::secureRandomBytesfn secureRandomBytes(n: Int64) -> List<Int64>Generates n cryptographically secure random bytes (OpenSSL RAND_bytes) as a raw byte list — unlike generateKey, no hex/string detour, so it's directly usable for PKCE code_verifier & co. (see the oauth2 module, issue #131). Throws on failure instead of returning a too-short list.

Example

import tinox.core.crypto;

// Simple SHA-256 hash
let digest: String = Crypto::sha256("Hello World");
println("SHA-256: " + digest);

// Verify HMAC signature
let secret: String = "mySecret";
let sig: String = Crypto::hmacSha256("payload", secret);
println("HMAC: " + sig);

// Generate random AES key and encrypt data
let key: String = Crypto::generateKey(32);
let cipher: String = Crypto::aesEncrypt("Secret message", key);
let plain: String  = Crypto::aesDecrypt(cipher, key);
println("Decrypted: " + plain);

// Password hashing via PBKDF2
let salt: String    = Crypto::generateKey(16);
let derived: String = Crypto::pbkdf2("password123", salt, 10000, 32);
println("Derived key: " + derived);

hash

import tinox.core.hash;

The hash module implements fast, non-cryptographic hash functions for strings and integers, as well as a combine function to fold multiple hashes into a single value. Typical use cases include hash maps, checksums, and fast data comparisons.

Classes & Methods

Class / MethodSignatureDescription
Hash::hashStringfn hashString(s: String) -> Int64Computes a djb2-style hash for the given string.
Hash::hashIntfn hashInt(n: Int64) -> Int64Computes a hash for an integer value using bit-mixing operations.
Hash::combinefn combine(h1: Int64, h2: Int64) -> Int64Combines two hash values into a single value (useful for composite keys).

Example

import tinox.core.hash;

// Compute string hash
let h1: Int64 = Hash::hashString("Tinox");
println("String hash: " + h1.toString());

// Compute integer hash
let h2: Int64 = Hash::hashInt(42);
println("Int hash: " + h2.toString());

// Hash composite key (e.g. (name, id))
let nameHash: Int64 = Hash::hashString("Alice");
let idHash: Int64   = Hash::hashInt(1001);
let combined: Int64 = Hash::combine(nameHash, idHash);
println("Combined hash: " + combined.toString());

// Simple bucket assignment
let buckets: Int64 = 16;
let bucket: Int64  = combined % buckets;
println("Bucket index: " + bucket.toString());

validation

import tinox.core.validation;

The validation module provides helper functions for input validation. It allows checking strings against common formats (email, URL, phone number), enforcing length rules, and applying arbitrary regular expressions.

Classes & Methods

Class / MethodSignatureDescription
Validation::isEmailfn isEmail(s: String) -> BoolChecks whether the string matches the format of an email address.
Validation::isUrlfn isUrl(s: String) -> BoolChecks whether the string is a valid HTTP or HTTPS URL.
Validation::isNumericfn isNumeric(s: String) -> BoolChecks whether the string consists exclusively of digits (0–9).
Validation::isAlphafn isAlpha(s: String) -> BoolChecks whether the string consists exclusively of letters (a–z, A–Z).
Validation::isAlphanumericfn isAlphanumeric(s: String) -> BoolChecks whether the string contains only letters and digits.
Validation::isPhonefn isPhone(s: String) -> BoolChecks whether the string has a valid phone number format (digits, +, -, spaces, brackets).
Validation::minLengthfn minLength(s: String, min: Int64) -> BoolChecks whether the string is at least min characters long.
Validation::maxLengthfn maxLength(s: String, max: Int64) -> BoolChecks whether the string is at most max characters long.
Validation::isInRangefn isInRange(n: Int64, min: Int64, max: Int64) -> BoolChecks whether the integer value n is within the range [min, max].
Validation::matchesPatternfn matchesPattern(s: String, pattern: String) -> BoolChecks whether the string matches the specified regular expression.

Example

import tinox.core.validation;

let email: String = "user@example.com";
if Validation::isEmail(email)
{
    println("Valid email address.");
}

let url: String = "https://tinox.io/api";
if Validation::isUrl(url) == false
{
    println("Invalid URL.");
}

// Check password rules
let password: String = "Secret42!";
let minOk: Bool = Validation::minLength(password, 8);
let maxOk: Bool = Validation::maxLength(password, 64);
println("Length OK: " + minOk.toString());

// Check numeric input
let age: String = "27";
if Validation::isNumeric(age)
{
    let n: Int64 = (Int64)age;
    println("Age in range: " + Validation::isInRange(n, 0, 120).toString());
}

// Custom pattern
let zip: String = "80331";
println("ZIP format: " + Validation::matchesPattern(zip, "^[0-9]{5}$").toString());

uuid

import tinox.core.uuid;

The uuid module enables generating and validating UUIDs (Universally Unique Identifiers) in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. It provides conversion functions between string representation and internal Uuid objects.

Classes & Methods

Class / MethodSignatureDescription
Uuid::generatefn generate() -> StringGenerates a new, random UUID and returns it as a string.
Uuid::fromStringfn fromString(s: String) -> UuidCreates a Uuid object from a UUID string.
Uuid::toStringfn toString() -> StringReturns the UUID of the object as a string.
Uuid::isValidfn isValid(s: String) -> BoolChecks whether the string is a syntactically correct UUID in the standard format (36 characters, hex + hyphens).

Example

import tinox.core.uuid;

// Generate new UUID
let id: String = Uuid::generate();
println("New UUID: " + id);

// Validate a UUID
let input: String = "123e4567-e89b-12d3-a456-426614174000";
if Uuid::isValid(input)
{
    println("Valid UUID.");
    let obj: Uuid = Uuid::fromString(input);
    println("UUID object: " + obj.toString());
}
else
{
    println("Invalid UUID input.");
}

// Typical use as record ID
let recordId: String = Uuid::generate();
println("New record with ID: " + recordId);

semaphore

import tinox.core.semaphore;

The semaphore module provides synchronization primitives for concurrent programs: a counting Semaphore to limit simultaneous accesses, an exclusive Mutex, and an RWLock for separate read and write locks.

Classes & Methods

Class / MethodSignatureDescription
Semaphore::newfn new(count: Int64) -> SemaphoreCreates a new semaphore with the specified initial counter.
Semaphore::acquirefn acquire(sem: Semaphore) -> NothingDecrements the counter by 1. Throws an error if the counter is already 0.
Semaphore::releasefn release(sem: Semaphore) -> NothingIncrements the counter by 1, releasing a resource.
Semaphore::tryAcquirefn tryAcquire(sem: Semaphore) -> BoolAttempts to decrement the counter; returns true on success without throwing.
Mutex::newfn new() -> MutexCreates a new, unlocked mutex.
Mutex::lockfn lock(mutex: Mutex) -> NothingLocks the mutex. Throws if it is already locked.
Mutex::unlockfn unlock(mutex: Mutex) -> NothingReleases the mutex.
Mutex::tryLockfn tryLock(mutex: Mutex) -> BoolAttempts to lock the mutex; returns true on success without throwing.
RWLock::newfn new() -> RWLockCreates a new RWLock with no active locks.
RWLock::readLockfn readLock(lock: RWLock) -> NothingAcquires a read lock. Throws if a write lock is active.
RWLock::readUnlockfn readUnlock(lock: RWLock) -> NothingReleases a previously acquired read lock.
RWLock::writeLockfn writeLock(lock: RWLock) -> NothingAcquires an exclusive write lock.
RWLock::writeUnlockfn writeUnlock(lock: RWLock) -> NothingReleases a previously acquired write lock.

Example

import tinox.core.semaphore;

// Semaphore: maximum 3 simultaneous connections
let sem: Semaphore = Semaphore::new(3);

if Semaphore::tryAcquire(sem)
{
    println("Connection acquired.");
    // ... work ...
    Semaphore::release(sem);
}

// Mutex: protect critical section
let mtx: Mutex = Mutex::new();
Mutex::lock(mtx);
println("Critical section");
Mutex::unlock(mtx);

// RWLock: multiple readers, exclusive writer
let rw: RWLock = RWLock::new();
RWLock::readLock(rw);
println("Reading data ...");
RWLock::readUnlock(rw);

RWLock::writeLock(rw);
println("Writing data ...");
RWLock::writeUnlock(rw);

pool

import tinox.core.pool;

The pool module provides a generic object pool. Expensive objects (e.g. database connections, network sockets) are kept in the pool and reused instead of being created anew each time. The maximum pool size is configurable.

Classes & Methods

Class / MethodSignatureDescription
Pool::newfn new(maxSize: Int64) -> Pool<T>Creates a new pool with the specified maximum size.
Pool::acquirefn acquire(pool: Pool<T>) -> TReturns an available object from the pool or creates a new one if the maximum size has not been reached. Throws "Pool exhausted" when the pool is full.
Pool::releasefn release(pool: Pool<T>, obj: T) -> NothingReturns a used object to the pool so it can be reused.
Pool::clearfn clear(pool: Pool<T>) -> NothingRemoves all objects from the pool (available and in-use ones).

Example

import tinox.core.pool;

// Pool for database connections (maximum 5)
let dbPool: Pool<DbConnection> = Pool::new(5);

// Acquire connection
let conn: DbConnection = Pool::acquire(dbPool);
let result: List<Row> = conn.query("SELECT * FROM users");
println("Rows: " + result.len().toString());

// Release connection
Pool::release(dbPool, conn);

// Second connection – reused from pool
let conn2: DbConnection = Pool::acquire(dbPool);
conn2.execute("UPDATE users SET active = 1 WHERE id = 42");
Pool::release(dbPool, conn2);

// Clear pool (e.g. on shutdown)
Pool::clear(dbPool);
println("Pool cleared.");

ratelimit

import tinox.core.ratelimit;

The ratelimit module implements two request limiting strategies: RateLimiter uses a sliding-window approach (fixed time window), while TokenBucket uses the token bucket approach with a configurable refill rate.

Classes & Methods

Class / MethodSignatureDescription
RateLimiter::newfn new(maxRequests: Int64, windowMs: Int64) -> RateLimiterCreates a new rate limiter with maximum request count and time window in milliseconds.
RateLimiter::allowfn allow(limiter: RateLimiter) -> BoolChecks whether another request is allowed. Returns true and registers the request, or false if the limit is reached.
RateLimiter::resetfn reset(limiter: RateLimiter) -> NothingResets all registered requests.
RateLimiter::remainingfn remaining(limiter: RateLimiter) -> Int64Returns the number of remaining allowed requests in the current time window.
TokenBucket::newfn new(capacity: Int64, refillRate: Float64) -> TokenBucketCreates a token bucket with capacity and refill rate (tokens per second).
TokenBucket::allowfn allow(bucket: TokenBucket) -> BoolChecks whether a token is available. Refills first and returns true if a token could be consumed.
TokenBucket::refillfn refill(bucket: TokenBucket) -> NothingRefills the bucket based on the time elapsed since the last refill.

Example

import tinox.core.ratelimit;

// Sliding window: 100 requests per minute
let limiter: RateLimiter = RateLimiter::new(100, 60000);

var i: Int64 = 0;
while i < 5
{
    if RateLimiter::allow(limiter)
    {
        println("Request " + i.toString() + " allowed. Remaining: "
                + RateLimiter::remaining(limiter).toString());
    }
    i = i + 1;
}

// Token bucket: capacity 10, 2 tokens/second
let bucket: TokenBucket = TokenBucket::new(10, 2.0);

if TokenBucket::allow(bucket)
{
    println("Request via token bucket allowed.");
}
else
{
    println("Token bucket empty – request rejected.");
}

cron

import tinox.core.cron;

The cron module enables scheduling recurring tasks via cron expressions in Unix format (5 fields: minute, hour, day, month, weekday). The CronScheduler manages multiple jobs and executes them on tick calls when the condition is met.

Classes & Methods

Class / MethodSignatureDescription
Cron::parsefn parse(expr: String) -> CronExprParses a cron expression (5 space-separated fields) into a CronExpr object. Throws on invalid format.
Cron::nextRunfn nextRun(expr: CronExpr) -> Int64Calculates the timestamp (ms since epoch) of the next scheduled run.
Cron::nextMinutefn nextMinute(current: Int64, field: String) -> Int64Helper function: returns the next minute matching the minute field.
Cron::getCurrentMinutefn getCurrentMinute(sec: Int64) -> Int64Helper function: calculates the current minute (0–59) from a Unix timestamp in seconds.
Cron::matchesfn matches(expr: CronExpr, timestamp: Int64) -> BoolChecks whether a cron expression applies to the specified timestamp.
CronScheduler::newfn new() -> CronSchedulerCreates a new, stopped scheduler with no jobs.
CronScheduler::addJobfn addJob(scheduler: CronScheduler, expr: String, callback: fnc() -> Nothing) -> NothingRegisters a new job with a cron expression and callback function.
CronScheduler::startfn start(scheduler: CronScheduler) -> NothingActivates the scheduler so that tick calls execute jobs.
CronScheduler::stopfn stop(scheduler: CronScheduler) -> NothingDeactivates the scheduler; further tick calls are ignored.
CronScheduler::tickfn tick(scheduler: CronScheduler) -> NothingChecks all registered jobs against the current timestamp and executes any that are due.

Example

import tinox.core.cron;

let scheduler: CronScheduler = CronScheduler::new();

// Execute every minute
CronScheduler::addJob(scheduler, "* * * * *", () => {
    println("Minutely job running.");
});

// Execute daily at 08:30
CronScheduler::addJob(scheduler, "30 8 * * *", () => {
    println("Daily morning report is being generated.");
});

CronScheduler::start(scheduler);

// Main loop (call tick e.g. every 60 s)
var tick: Int64 = 0;
while tick < 3
{
    CronScheduler::tick(scheduler);
    tick = tick + 1;
}

CronScheduler::stop(scheduler);
println("Scheduler stopped.");

events

import tinox.core.events;

The events module implements the observer pattern via an EventEmitter. Listeners can be registered for named events, subscribed once, or removed again. When an event is emitted, all registered handlers are called with a JsonValue payload.

Classes & Methods

Class / MethodSignatureDescription
EventEmitter::newfn new() -> EventEmitterCreates a new, empty event emitter.
EventEmitter::onfn on(emitter: EventEmitter, event: String, handler: fnc(JsonValue) -> Nothing) -> NothingRegisters a permanent listener for the specified event.
EventEmitter::oncefn once(emitter: EventEmitter, event: String, handler: fnc(JsonValue) -> Nothing) -> NothingRegisters a listener that is executed the first time the event occurs.
EventEmitter::emitfn emit(emitter: EventEmitter, event: String, data: JsonValue) -> NothingEmits the specified event and passes the payload to all registered handlers.
EventEmitter::removeListenerfn removeListener(emitter: EventEmitter, event: String, handler: fnc(JsonValue) -> Nothing) -> NothingRemoves a specific listener for an event.
EventEmitter::removeAllListenersfn removeAllListeners(emitter: EventEmitter, event: String) -> NothingRemoves all listeners for the specified event.
EventEmitter::listenerCountfn listenerCount(emitter: EventEmitter, event: String) -> Int64Returns the number of registered listeners for an event.

Example

import tinox.core.events;

let emitter: EventEmitter = EventEmitter::new();

// Permanent listener
EventEmitter::on(emitter, "order", (data: JsonValue) => {
    println("New order: " + data.toString());
});

// One-time listener
EventEmitter::once(emitter, "start", (data: JsonValue) => {
    println("Application started.");
});

println("Listener count for 'order': "
        + EventEmitter::listenerCount(emitter, "order").toString());

// Emit events
EventEmitter::emit(emitter, "start", Json::null());
EventEmitter::emit(emitter, "order", Json::parse("{\"id\": 42}"));

// Remove all listeners
EventEmitter::removeAllListeners(emitter, "order");
println("Listeners after removal: "
        + EventEmitter::listenerCount(emitter, "order").toString());

time

import tinox.core.time;

The time module provides utilities for time operations: The Time class is the entry-point factory, Duration represents time spans, Timer measures a defined start-stop interval, and Stopwatch is the counterpart with an additional isRunning() query.

Classes & Methods

Class / MethodSignatureDescription
Time::nowfn now() -> Int64Returns the current system time in milliseconds since the Unix epoch.
Time::sleepfn sleep(ms: Int64) -> NothingPauses execution for the specified number of milliseconds.
Time::newTimerfn newTimer() -> TimerCreates a new Timer instance.
Time::newStopwatchfn newStopwatch() -> StopwatchCreates a new Stopwatch instance.
Time::newDurationfn newDuration(seconds: Int64) -> DurationCreates a Duration from seconds.
Time::durationFromMinutesfn durationFromMinutes(minutes: Int64) -> DurationCreates a Duration from minutes.
Time::durationFromHoursfn durationFromHours(hours: Int64) -> DurationCreates a Duration from hours.
Duration::newfn new(seconds: Int64) -> DurationCreates a Duration instance directly from seconds.
Duration::fromMinutesfn fromMinutes(minutes: Int64) -> DurationCreates a Duration from minutes (converted to seconds internally).
Duration::fromHoursfn fromHours(hours: Int64) -> DurationCreates a Duration from hours (converted to seconds internally).
Duration::getSecondsfn getSeconds() -> Int64Returns the duration in seconds.
Duration::getMinutesfn getMinutes() -> Int64Returns the duration in whole minutes (truncated).
Duration::getHoursfn getHours() -> Int64Returns the duration in whole hours (truncated).
Duration::addfn add(other: Duration) -> DurationAdds another Duration and returns a new instance.
Duration::subfn sub(other: Duration) -> DurationSubtracts a Duration and returns a new instance.
Timer::newfn new() -> TimerCreates a new, not-yet-started timer.
Timer::startfn start() -> NothingStarts the timer and saves the current time.
Timer::stopfn stop() -> NothingStops the timer and saves the end time.
Timer::elapsedfn elapsed() -> Int64Returns the elapsed time in milliseconds. If not yet stopped, measures time until now.
Timer::elapsedSecondsfn elapsedSeconds() -> Int64Returns the elapsed time in whole seconds.
Stopwatch::newfn new() -> StopwatchCreates a new, not-yet-running stopwatch.
Stopwatch::startfn start() -> NothingStarts the stopwatch.
Stopwatch::stopfn stop() -> NothingStops the stopwatch.
Stopwatch::isRunningfn isRunning() -> BoolReturns true if the stopwatch is currently running.
Stopwatch::elapsedfn elapsed() -> Int64Returns the elapsed time in milliseconds: while running, the time since start(); after stop(), the frozen start-to-stop span; otherwise 0.

Example

import tinox.core.time;

// Current time
let now: Int64 = Time::now();
println("Now (ms): " + now.toString());

// Timer for an operation
let timer: Timer = Time::newTimer();
timer.start();
// ... work ...
timer.stop();
println("Duration: " + timer.elapsedSeconds().toString() + " s");

// Stopwatch
let sw: Stopwatch = Time::newStopwatch();
sw.start();
println("Running: " + sw.isRunning().toString());
sw.stop();
println("Elapsed ms: " + sw.elapsed().toString());

// Combine durations
let d1: Duration = Time::newDuration(3600);    // 1 hour
let d2: Duration = Time::durationFromMinutes(30);
let total: Duration = d1.add(d2);
println("Total in minutes: " + total.getMinutes().toString());

cache

import tinox.core.cache;

The cache module provides a generic LRU cache (Least Recently Used). When the maximum size is reached, the least recently used entry is automatically evicted. The LruCache class is a slim wrapper around the internal Cache class.

Classes & Methods

Class / MethodSignatureDescription
Cache::newfn new(maxSize: Int64) -> Cache<K, V>Creates a new cache with the specified maximum number of entries.
Cache::getfn get(cache: Cache<K, V>, key: K) -> Option<V>Returns the value for the key as an Option and updates the access order.
Cache::setfn set(cache: Cache<K, V>, key: K, value: V) -> NothingSets an entry. If the maximum size is exceeded, the oldest entry is removed.
Cache::removefn remove(cache: Cache<K, V>, key: K) -> BoolRemoves the entry with the specified key. Returns true if the key was present.
Cache::clearfn clear(cache: Cache<K, V>) -> NothingRemoves all entries from the cache.
Cache::touchfn touch(cache: Cache<K, V>, key: K) -> NothingMarks a key as most recently used (moves it to the end of the LRU list).
LruCache::newfn new(maxSize: Int64) -> LruCache<K, V>Creates a new LRU cache wrapper with the specified maximum size.
LruCache::getfn get(cache: LruCache<K, V>, key: K) -> Option<V>Returns the value for the key as an Option.
LruCache::setfn set(cache: LruCache<K, V>, key: K, value: V) -> NothingSets an entry in the LRU cache.

Example

import tinox.core.cache;

// LRU cache with maximum 3 entries
let lru: LruCache<String, String> = LruCache::new(3);

LruCache::set(lru, "a", "Apple");
LruCache::set(lru, "b", "Banana");
LruCache::set(lru, "c", "Cherry");

// Read entry
let val: Option<String> = LruCache::get(lru, "b");
if val.isSome()
{
    println("Found: " + val.unwrap());
}

// Fourth entry evicts the oldest (here "a")
LruCache::set(lru, "d", "Date");

let old: Option<String> = LruCache::get(lru, "a");
println("'a' still in cache: " + old.isSome().toString());

// Clear cache
Cache::clear(lru.cache);
println("Cache cleared.");

option

import tinox.core.option;

The option module provides the generic type Option<T>, which represents either a value (Some) or the absence of a value (None). Internally a List<T> is used to avoid the need for a null value for T.

Classes & Methods

Class / MethodSignatureDescription
Option::nonefn none() -> Option<T>Creates an empty option (no value).
Option::somefn some(value: T) -> Option<T>Creates an option containing the specified value.
Option::isSomefn isSome() -> BoolReturns true if the option contains a value.
Option::isNonefn isNone() -> BoolReturns true if the option is empty.
Option::unwrapfn unwrap() -> TReturns the contained value. Throws "Option is None" if the option is empty.
Option::unwrapOrfn unwrapOr(defaultValue: T) -> TReturns the contained value or the specified default value if the option is empty.
Option::mapfn map<U>(transform: fnc(T) -> U) -> Option<U>Applies a transformation function to the contained value and returns a new option.
Option::andThenfn andThen<U>(transform: fnc(T) -> Option<U>) -> Option<U>Chaining: applies a function that itself returns an option (flatmap).
Option::orElsefn orElse(fallback: fnc() -> Option<T>) -> Option<T>Returns the current option if it has a value; otherwise the result of the fallback call.

Example

import tinox.core.option;

class Main
{
    fnc findUser(id: Int64) -> Option<String>
    {
        if id == 1
        {
            return Option::some("Alice");
        }
        return Option::none();
    }

    fnc main() -> Int32
    {
        // Extract value with fallback
        let name: String = findUser(1).unwrapOr("Unknown");
        println("Found: " + name);

        // Transformation with map
        let upper: Option<String> = findUser(1).map(fnc(n: String) -> String { return n.toUpperCase(); });
        println("Uppercase: " + upper.unwrapOr("–"));

        // Chaining with andThen
        let result: Option<Int64> = findUser(1)
            .andThen(fnc(n: String) -> Option<Int64> {
                if n.len() > 3 { return Option::some(n.len()); }
                return Option::none();
            });
        println("Length: " + result.unwrapOr(0).toString());

        // orElse fallback
        let alternative: Option<String> = findUser(99)
            .orElse(fnc() -> Option<String> { return Option::some("Guest"); });
        println("Alternative: " + alternative.unwrap());
        return 0;
    }
}

result

import tinox.core.result;

The result module provides the generic type Result<T>, which represents either a success value (Ok) or an error message as a string (Err). It enables explicit, type-safe error handling without exceptions.

Classes & Methods

Class / MethodSignatureDescription
Result::okfn ok(value: T) -> Result<T>Creates a successful result with the specified value.
Result::errfn err(error: String) -> Result<T>Creates an error result with the specified error message.
Result::isOkfn isOk() -> BoolReturns true if the result is successful.
Result::isErrfn isErr() -> BoolReturns true if the result contains an error.
Result::unwrapfn unwrap() -> TReturns the success value. Throws the error message if the result is an error.
Result::unwrapOrfn unwrapOr(defaultValue: T) -> TReturns the success value or the specified default value if an error is present.
Result::getErrorfn getError() -> StringReturns the error message (empty string if no error).
Result::mapfn map<U>(transform: fnc(T) -> U) -> Result<U>Applies a transformation function to the success value. Errors are passed through unchanged.
Result::andThenfn andThen<U>(transform: fnc(T) -> Result<U>) -> Result<U>Chains operations that themselves return a Result (flatmap). Errors are short-circuited.
Result::mapErrfn mapErr(transform: fnc(String) -> String) -> Result<T>Transforms the error message. Success values are passed through unchanged.

Example

import tinox.core.result;

class Main
{
    fnc divide(a: Int64, b: Int64) -> Result<Int64>
    {
        if b == 0
        {
            return Result::err("Division by zero");
        }
        return Result::ok(a / b);
    }

    fnc main() -> Int32
    {
        // Simple usage
        let r: Result<Int64> = divide(10, 2);
        if r.isOk()
        {
            println("Result: " + r.unwrap().toString());
        }

        // Error case
        let err: Result<Int64> = divide(5, 0);
        println("Error: " + err.getError());
        println("Fallback: " + err.unwrapOr(-1).toString());

        // Chaining
        let chain: Result<String> = divide(100, 4)
            .map(fnc(n: Int64) -> String { return "Value: " + n.toString(); })
            .mapErr(fnc(e: String) -> String { return "Calculation failed – " + e; });
        println(chain.unwrapOr("No result"));
        return 0;
    }
}

logger

import tinox.core.logger;

The logger module provides a structured logging system with configurable log levels. Each logger has a name and outputs messages with a timestamp, level, and name. The LogLevel class defines the four levels Debug, Info, Warn, and Error.

Setting the environment variable TINOX_LOG_JSON=true makes every Logger (including instances injected via @Log) emit a JSON object with the fields timestamp, level, logger, and message instead of the plain-text line – handy for structured log pipelines. Without the variable (or with any value other than "true"), the previous plain-text format [timestamp] [LEVEL] [name] message is unchanged.

Classes & Methods

Class / MethodSignatureDescription
Logger::newfn new(name: String) -> LoggerCreates a new logger with the specified name. The default level is Info.
Logger::debugfn debug(logger: Logger, message: String) -> NothingOutputs a debug message if the level is <= Debug.
Logger::infofn info(logger: Logger, message: String) -> NothingOutputs an info message if the level is <= Info.
Logger::warnfn warn(logger: Logger, message: String) -> NothingOutputs a warning message if the level is <= Warn.
Logger::errorfn error(logger: Logger, message: String) -> NothingOutputs an error message if the level is <= Error.
Logger::setLevelfn setLevel(logger: Logger, level: LogLevel) -> NothingSets the minimum log level of the logger. Messages below the level are suppressed.
Logger::logfn log(logger: Logger, level: String, message: String) -> NothingInternal output method: writes a formatted line with timestamp, level, and logger name.
LogLevel::Debugfn Debug() -> LogLevelReturns the log level Debug (value 0).
LogLevel::Infofn Info() -> LogLevelReturns the log level Info (value 1).
LogLevel::Warnfn Warn() -> LogLevelReturns the log level Warn (value 2).
LogLevel::Errorfn Error() -> LogLevelReturns the log level Error (value 3).

Example

import tinox.core.logger;

// Create logger
let log: Logger = Logger::new("my-app");

// Default level is Info
Logger::info(log, "Application started.");
Logger::warn(log, "Configuration file not found – using defaults.");
Logger::error(log, "Database connection failed.");

// Debug messages are invisible by default
Logger::debug(log, "This text does not appear yet.");

// Set level to Debug
Logger::setLevel(log, LogLevel::Debug());
Logger::debug(log, "Debug mode active. All messages are output.");

// Module-specific logger
let dbLog: Logger = Logger::new("database");
Logger::setLevel(dbLog, LogLevel::Warn());
Logger::info(dbLog, "This info is suppressed.");
Logger::warn(dbLog, "Connection pool nearly exhausted.");

JSON Output

// Set before the process starts (or via Env::setVar):
// TINOX_LOG_JSON=true

let log: Logger = Logger::new("my-app");
Logger::info(log, "Application started.");
// {"timestamp":"...","level":"INFO","logger":"my-app","message":"Application started."}

ORM & Lambda Query DSL

Tinox provides a type-safe ORM inspired by JPA/QueryDSL. Classes are annotated as database tables, and all queries run through a type-safe lambda DSL — no string-based queries.

Annotations

AnnotationTargetDescription
@EntityClassMarks the class as a database entity
@Table("name")ClassSets the table name (default: lowercase class name)
@IdFieldPrimary key
@Column("col")FieldMaps field to a DB column (default: field name)
@GeneratedValueFieldID generated by the DB (AUTO_INCREMENT / SERIAL)
@NotNullFieldColumn is NOT NULL

Entity Definition

@Entity
@Table("users")
class User {
    @Id @GeneratedValue
    var id: Int64;
    @Column("name")
    var name: String;
    @Column("age")
    var age: Int64;
    @Column("active")
    var active: Bool;
}

tinox.toml – [database] Configuration

# PostgreSQL
[database]
driver = "postgres"
url    = "postgres://user:pass@localhost:5432/mydb"
pool   = 10

# MySQL / MariaDB
[database]
driver = "mysql"
url    = "mysql://user:pass@localhost:3306/mydb"

# SQLite (no DB server required)
[database]
driver = "sqlite"
url    = "sqlite:///path/to/database.db"

Lambda Query DSL

// All users over 18, sorted alphabetically, max 10 results
let adults: List<User> = DB.of(User)
    .filter(u -> u.age > 18)
    .orderBy(u -> u.name)
    .limit(10)
    .list();

// Read a single user
let alice: User = DB.of(User)
    .filter(u -> u.name == "Alice")
    .first();

// Count active users
let n: Int64 = DB.of(User)
    .filter(u -> u.active == true)
    .count();

Lambda → SQL Translation Table

Lambda ExpressionSQL Fragment
u.age > 18age > $1
u.name == "Alice"name = $1
u.name != "Bob"name != $1
u.name.startsWith("A")name LIKE 'A%'
u.name.endsWith("z")name LIKE '%z'
u.name.contains("oo")name LIKE '%oo%'
expr1 && expr2(pred1) AND (pred2)
expr1 || expr2(pred1) OR (pred2)
!exprNOT (pred)

Save, Update, Delete

// INSERT (ID assigned by the DB)
let user: User = User {};
user.name = "Alice";
user.age  = 30;
let saved: User = DB.of(User).save(user);

// DELETE
DB.of(User).delete(saved);

// Save multiple entities
let list: List<User> = List::new();
list.add(user1);
list.add(user2);
DB.of(User).saveAll(list);

Error Handling

Database errors are currently logged to stderr. Integration with try/catch is planned for a future release. Connection errors at startup terminate the program with a descriptive message.