Topic
push.rocks
@push.rocks/smartdns is a TypeScript-first DNS toolkit with Rust binaries behind the wire-level DNS work.
The public surface stays in TypeScript: create a DNS client, choose a resolution strategy, register authoritative server handlers, retrieve ACME certificates, and wire the package into a larger service. Rust handles the parts where DNS is byte-oriented: UDP queries, DNS-over-HTTPS wire-format requests, packet parsing and encoding, async UDP and HTTPS listeners, and DNSSEC signing.
This is the same TypeScript/Rust split beta.news covered in the foss.global bridge architecture dispatch. TypeScript owns product shape and integration. Rust owns the data path. SmartDNS applies that pattern to DNS without turning the package into a general-purpose recursive resolver.
Three entry points
The package ships three import surfaces:
import { Smartdns } from '@push.rocks/smartdns/client';
import { DnsServer } from '@push.rocks/smartdns/server';
import { dnsClientMod, dnsServerMod } from '@push.rocks/smartdns';The client entry point resolves records. The server entry point runs an authoritative DNS server with TypeScript handlers. The root entry point re-exports both modules.
The current public package version is 7.9.3. The export map points @push.rocks/smartdns/client to dist_ts_client, @push.rocks/smartdns/server to dist_ts_server, and the root package to dist_ts. The build pipeline runs tsbuild tsfolders --web and tsrust, so the TypeScript and Rust outputs ship together.
The major architecture changes landed in the 7.7.x and 7.8.x line. Version 7.7.0 added the Rust server backend and TypeScript bridge. Version 7.8.0 added the Rust DNS client binary for UDP and DoH. Version 7.8.1 removed the synchronous TypeScript packet-processing fallback; current raw packet processing requires the Rust bridge path.
The client: system resolver, Rust UDP, or Rust DoH
Smartdns supports five resolution strategies:
prefer-system: try the operating system resolver first, then fall back to Rust DoH;system: use only Node's DNS module;doh: use DNS-over-HTTPS through the Rust client;udp: use raw UDP DNS through the Rust client;prefer-udp: try Rust UDP first, then fall back to Rust DoH.
The difference is operational. The system resolver path honors local host behavior and does not start Rust. UDP and DoH use rustdns-client, which is spawned lazily on the first query that needs it and can be stopped with destroy().
import { Smartdns } from '@push.rocks/smartdns/client';
const dns = new Smartdns({ strategy: 'prefer-udp', timeoutMs: 5000 });
const aRecords = await dns.getRecordsA('example.com');
const mxRecords = await dns.getRecords('example.com', 'MX');
const txtRecords = await dns.getRecordsTxt('example.com');
dns.destroy();The Rust client builds DNS wire-format queries with the recursion desired flag set. It adds EDNS0 with the DNSSEC OK bit, sends UDP queries to an upstream resolver or RFC 8484 DoH POST requests, parses the DNS response, decodes common RDATA types, and reports the upstream AD flag back to TypeScript as dnsSecEnabled.
Type-specific helpers cover A, AAAA, TXT, and NS lookups. Generic queries support A, AAAA, CNAME, MX, TXT, NS, SOA, PTR, and SRV. That makes the client useful for propagation checks, certificate workflows, DNS validation in infrastructure services, and application code that needs predictable DNS behavior without making each consumer parse DNS packets.
The server: authoritative DNS with Rust I/O and TypeScript handlers
The server side is DnsServer. It is an authoritative DNS server, not a recursive resolver. Rust owns UDP socket handling, DNS packet parsing and encoding, DNS-over-HTTPS over Hyper and Rustls, DNSSEC key and signature work, and the IPC management loop. TypeScript owns server lifecycle, handler registration, ACME orchestration, domain authorization, and query events.
import { DnsServer } from '@push.rocks/smartdns/server';
const server = new DnsServer({
udpPort: 53,
httpsPort: 443,
httpsKey: '...pem...',
httpsCert: '...pem...',
dnssecZone: 'example.com',
primaryNameserver: 'ns1.example.com',
});
server.registerHandler('*.example.com', ['A'], (question) => ({
name: question.name,
type: 'A',
class: 'IN',
ttl: 300,
data: '10.0.0.1',
}));
await server.start();When a DNS query arrives, the Rust process emits a dnsQuery event over JSON IPC. TypeScript resolves the question against registered handlers. The answer list goes back to Rust, which assembles the DNS response and signs RRsets when DNSSEC was requested.
Handlers use glob patterns through minimatch, and multiple handlers can contribute records to the same response. That fits dynamic authoritative DNS cases: wildcard service records, generated internal hostnames, tenant-specific answers, ACME DNS-01 challenge records, and dashboard-managed zones.
The 7.9.3 release fixed a real DNS edge case in that handler path. Query names and record types are now normalized before matching, so DNS 0x20 mixed-case randomization still resolves registered records.
DNSSEC is in the Rust layer
DNSSEC is enabled through dnssecZone. The TypeScript server configuration currently sends ECDSA as the default algorithm, and the Rust key implementation covers ECDSA P-256 and ED25519. DNSKEY answers and RRSIG generation are produced in Rust, with RRsets serialized canonically before signing.
That placement is intentional. DNSSEC signing is sensitive to wire format details: canonical ordering, lower-case owner names, TTL handling, record-set boundaries, and the exact RDATA bytes. Those are better handled next to the Rust packet encoder than in an application-level fallback.
DNS-over-HTTPS on both sides
SmartDNS uses DNS-over-HTTPS in both directions.
As a client, rustdns-client sends RFC 8484 application/dns-message requests. Its default DoH endpoint is Cloudflare's https://cloudflare-dns.com/dns-query, and the default UDP upstream is 1.1.1.1:53.
As a server, the Rust backend can expose a DoH listener over HTTPS. The TypeScript options provide HTTPS bind interface, port, certificate, and key. ACME support can retrieve Let's Encrypt certificates through DNS-01 challenge records registered temporarily in the same handler system.
Manual binding and query events
Most deployments can let DnsServer.start() bind UDP and HTTPS directly. The options also support explicit UDP and HTTPS bind interfaces, so the server can be restricted to localhost, a LAN address, or different addresses per protocol.
Manual UDP and HTTPS modes let an outer service own socket placement. In current Rust mode, raw packet processing goes through processRawDnsPacketAsync(); the server must be started
@push.rocks/smartvm is a TypeScript control layer for Amazon Firecracker. It downloads and caches Firecracker binaries, resolves bootable base images, starts microVMs through the Firecracker Unix-socket API, creates host networking, stages ephemeral drives, applies optional egress controls, and cleans up the mess when a VM is done.
The point is simple: Firecracker gives you strong, lightweight VM isolation. smartvm gives you a programmable way to use it without writing the same shell scripts, socket calls, TAP setup, bridge setup, and cleanup code for every project.
Why Firecracker Needs a Control Layer
Firecracker is intentionally small. It is a virtual machine monitor, not a platform. That is part of its strength. It exposes an HTTP API over a Unix domain socket and expects the host to provide the rest: kernel image, root filesystem, network devices, boot arguments, process lifecycle, and cleanup.
That leaves every user building the same operational layer:
- Find or download the right Firecracker binary.
- Prepare a kernel and root filesystem.
- Start the VMM with an API socket.
- Wait for the socket and API to become ready.
- Send pre-boot config in the right order.
- Create TAP devices and connect them to host networking.
- Decide how VM traffic leaves the host.
- Stop the VMM and remove sockets, TAPs, bridges, routes, and temp files.
smartvm wraps that host plumbing behind typed TypeScript classes.
SmartVM
ImageManager Firecracker binaries, manual kernels, manual rootfs files
BaseImageManager cached Firecracker CI or hosted base image bundles
NetworkManager TAPs, bridge, NAT, firewall, WireGuard egress
MicroVM one Firecracker VM and its state machine
FirecrackerProcess child process and socket readiness
SocketClient HTTP over Unix socket
VMConfig config validation and payload conversion
Your application describes the VM. smartvm handles the host-side sequence.
Ecosystem note. push.rocks packages are reusable infrastructure modules in the foss.global stack. smartvm stays at the control-layer level: it orchestrates Firecracker binaries, sockets, networking, and cleanup while the caller owns the host and workload policy.
The Minimal Boot Path
The happy path is short: create a SmartVM, resolve a base image, create a VM, start it, and clean it up.
import { SmartVM } from '@push.rocks/smartvm';
const smartvm = new SmartVM({
dataDir: '/tmp/.smartvm',
runtimeDir: '/dev/shm/.smartvm/runtime',
});
const baseImage = await smartvm.ensureBaseImage({ preset: 'latest' });
const vm = await smartvm.createVM({
id: 'hello-firecracker',
bootSource: {
kernelImagePath: baseImage.kernelImagePath,
bootArgs: baseImage.bootArgs,
},
machineConfig: {
vcpuCount: 1,
memSizeMib: 256,
},
drives: [
{
driveId: 'rootfs',
pathOnHost: baseImage.rootfsPath,
isRootDevice: true,
isReadOnly: baseImage.rootfsIsReadOnly,
},
],
});
try {
await vm.start();
console.log(vm.state); // running
console.log(await vm.getVersion());
console.log(await vm.getInfo());
} finally {
if (vm.state === 'running' || vm.state === 'paused') {
await vm.stop();
}
await vm.cleanup();
await smartvm.cleanup();
}
That snippet hides a lot of host work. Firecracker is downloaded or reused. A base image bundle is resolved and cached. A per-VM runtime directory is created. A Unix socket path is assigned. Writable drives are staged if needed. Firecracker is started as a child process. The API socket is polled until ready. Configuration is sent through the socket. The instance is booted. Cleanup tears down the process and host resources owned by the instance.
Real Isolation, Real Host Requirements
smartvm is TypeScript, but it is not pretending that Firecracker is portable JavaScript. Firecracker is a Linux/KVM technology.
You need a Linux host with /dev/kvm. If you use networking, you need privileges for TAP devices, bridges, IP forwarding, iptables, policy routing, and optional WireGuard setup. The host also needs normal system tools such as curl, tar, ip, sysctl, and iptables; WireGuard routing needs wg.
That constraint is useful to state clearly. smartvm is not a local toy VM emulator. It is a typed operations layer for real microVMs on a real Linux host.
Ephemeral by Default
The most important design choice in smartvm is its runtime model. VMs are treated as disposable execution units unless you opt into persistence.
By default, runtime files go into tmpfs when available:
| Artifact | Default location | Behavior |
|---|---|---|
| Firecracker binaries | /tmp/.smartvm/bin |
Cached for reuse |
| Base images | /tmp/.smartvm/base-images |
Cached with retention |
| VM sockets | /dev/shm/.smartvm/runtime/<vmId>/firecracker.sock |
Per-VM, deleted on cleanup |
| Writable drives | /dev/shm/.smartvm/runtime/<vmId>/drives/* |
Copied before boot, deleted on cleanup |
| Read-only drives | Original path | Reused directly unless explicitly staged |
The default is: immutable base image, staged writable copy, no accidental mutation of cached root filesystems.
const vm = await smartvm.createVM({
bootSource: {
kernelImagePath: baseImage.kernelImagePath,
bootArgs: baseImage.bootArgs,
},
machineConfig: {
vcpuCount: 1,
memSizeMib: 256,
},
drives: [
{
driveId: 'rootfs',
pathOnHost: '/images/rootfs.ext4',
isRootDevice: true,
isReadOnly: false,
ephemeral: true,
},
],
});
If you need persistent state, say so explicitly:
const vm = await smartvm.createVM({
bootSource: {
kernelImagePath: '/images/vmlinux',
bootArgs: 'console=ttyS0 reboot=k panic=1 pci=off',
},
machineConfig: {
vcpuCount: 2,
memSizeMib: 512,
},
drives: [
{
driveId: 'state',
pathOnHost: '/var/lib/my-vm/state.ext4',
isRootDevice: true,
isReadOnly: false,
ephemeral: false,
},
],
});
This default is the right bias for worker pools, test isolation, CI jobs, sandboxed execution, disposable service instances, and workloads where durable state belongs in an external database, object store, or explicit persistent volume.
Base Images Without Checking Rootfs Files Into Git
Firecracker needs a kernel image and a root filesystem. smartvm has two layers for that.
ImageManager is the lower-level helper for binaries, kernels, rootfs downloads, blank rootfs creation, and cloning. It is useful when you already know what files you want.
BaseImageManager is the higher-level path. It resolves bootable Firecracker CI image bundles through presets or a project-owned hosted manifest.
const latest = await smartvm.ensureBaseImage();
const lts = await smartvm.ensureBaseImage({ preset: 'lts' });
const hosted = await smartvm.ensureBaseImage({
preset: 'hosted',
manifestUrl: 'https://assets.example.com/smartvm/manifest.json',
});
The presets are deliberately pragmatic:
| Preset | Meaning |
|---|---|
latest |
Resolve the latest Firecracker release and matching CI demo artifacts |
lts |
Use the pinned Firecracker CI train v1.7 and Firecracker v1.7.0 |
hosted |
Use your own manifest with kernel/rootfs artifact metadata |
Cached base image bundles are stored under /tmp/.smartvm/base-images by default. The cache keeps two bundles unless configured otherwise. Cached artifacts are checked for
@push.rocks/smartproxy is a production proxy that handles TCP, TLS, HTTP reverse proxying, WebSockets, UDP, QUIC/HTTP3, load balancing, and kernel-level NFTables forwarding — all from a single route-based TypeScript API. Under the hood, a Rust binary does every byte of networking. Your TypeScript code just describes what should happen. The Rust engine figures out how — fast.
The Problem with Proxy Configuration
If you've ever set up nginx, HAProxy, or Envoy, you know the drill: write YAML or a bespoke config language, restart the process, hope the syntax is right, grep through docs for the directive name you forgot. There's no type checking, no autocomplete, no way to express "route this domain to backend A unless the path starts with /api, in which case terminate TLS and forward to backend B with rate limiting."
SmartProxy replaces all of that with typed TypeScript objects. A route is a match/action pair. The match says what traffic to capture. The action says what to do with it. TypeScript catches your mistakes before the proxy even starts.
30 Seconds to HTTPS
import { SmartProxy } from '@push.rocks/smartproxy';
const proxy = new SmartProxy({
acme: {
email: 'ssl@yourdomain.com',
useProduction: true
},
routes: [
// Redirect HTTP → HTTPS
{
name: 'redirect-to-https',
match: { ports: 80, domains: 'app.example.com' },
action: {
type: 'socket-handler',
socketHandler: SocketHandlers.httpRedirect('https://{domain}{path}', 301)
}
},
// Terminate TLS, forward to backend
{
name: 'app-https',
match: { ports: 443, domains: 'app.example.com' },
action: {
type: 'forward',
targets: [{ host: 'localhost', port: 3000 }],
tls: { mode: 'terminate', certificate: 'auto' }
}
}
]
});
await proxy.start();
Two routes. The first catches HTTP on port 80 and redirects to HTTPS using a built-in socket handler with template variables ({domain}, {path}). The second terminates TLS on port 443 with an automatic Let's Encrypt certificate and forwards plain HTTP to your backend. No certificate files to manage. No cron jobs for renewal. The Rust engine handles ACME challenges, stores nothing to disk, and calls back into your TypeScript code if you want custom persistence.
The Route Model
Every route in SmartProxy follows the same shape:
{
name: 'api-route',
match: {
ports: 443,
domains: 'api.example.com',
path: '/v1/*'
},
action: {
type: 'forward',
targets: [{ host: 'backend', port: 8080 }],
tls: { mode: 'terminate', certificate: 'auto' }
}
}
The match block supports ports (single, array, or ranges), domains (exact or wildcard), paths, transport protocol (tcp, udp, or all), application-layer protocol (http, tcp, quic), client IP ranges, TLS versions, and HTTP headers. The action block is either forward (proxy to backends) or socket-handler (hand the raw socket to your TypeScript function). Every field is typed. The match criteria compose — you can have a route that only fires for QUIC traffic from a specific IP range on a specific port with a specific SNI hostname.
Routes are evaluated in priority order. First match wins. You can update them at runtime with proxy.updateRoutes() — the operation is atomic and mutex-locked, so in-flight connections aren't affected.
Three TLS Modes
SmartProxy supports three ways to handle TLS, and the choice matters:
Passthrough routes encrypted traffic based on the SNI hostname without decrypting it. The backend handles TLS. The proxy never sees plaintext. This is what you want when you can't or don't want to terminate TLS at the proxy layer.
{
name: 'tls-passthrough',
match: { ports: 443, domains: 'secure.example.com' },
action: {
type: 'forward',
targets: [{ host: 'backend-that-handles-tls', port: 8443 }],
tls: { mode: 'passthrough' }
}
}
Terminate decrypts at the proxy and forwards plain HTTP to the backend. This is the standard reverse proxy model. The proxy can inspect HTTP headers, match paths, add headers, and do request-level routing.
Terminate-and-reencrypt decrypts at the proxy, then re-encrypts to the backend. HTTP traffic gets full per-request routing (Host header, path matching) via the HTTP proxy; non-HTTP traffic uses a raw TLS-to-TLS tunnel. This is for zero-trust environments where traffic must be encrypted on every hop.
The Rust Engine
All networking — TCP listeners, TLS handshakes, HTTP parsing, connection pooling, security enforcement, metrics collection, UDP sockets, QUIC — runs inside a Rust binary. The TypeScript process communicates with it over JSON IPC on stdin/stdout. This isn't an FFI binding or a WASM module. It's a separate process.
The architecture diagram tells the story:
┌─────────────────────────────────────────────────┐
│ Your Application │
│ (TypeScript — routes, config, handlers) │
└──────────────────┬──────────────────────────────┘
│ IPC (JSON over stdin/stdout)
┌──────────────────▼──────────────────────────────┐
│ Rust Proxy Engine │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ TCP/TLS │ │ HTTP │ │ Route │ │
│ │ Listener│ │ Proxy │ │ Matcher │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ UDP │ │Security │ │ Metrics │ │
│ │ QUIC │ │ Enforce │ │ Collect │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────┘
The process split is deliberate. The Rust engine doesn't parse config files. The TypeScript side never touches a raw packet. When a route uses a JavaScript socket handler or a dynamic host function (a callback that can't be serialized to Rust), the Rust engine relays that connection back to a TypeScript-side Unix socket server. Everything else stays in Rust.
Ecosystem note. The TypeScript/Rust bridge pattern keeps configuration and API ergonomics in TypeScript while the Rust subprocess owns the packet path. smartproxy is one of the clearest examples because route objects stay typed and byte handling stays native.
Load Balancing with Health Checks
{
name: 'load-balanced-app',
match: { ports: 443, domains: 'app.example.com' },
action: {
type: 'forward',
targets: [
{ host: 'server1.internal', port: 8080 },
{ host: 'server2.internal', port: 8080 },
{ host: 'server3.internal', port: 8080 }
],
tls: { mode: 'terminate', certificate: 'auto' },
loadBalancing: {
algorithm: 'round-robin',
healthCheck: {
path: '/health',
interval: 30000,
timeout: 5000,
unhealthyThreshold: 3,
healthyThreshold: 2
}
}
}
}
Three algorithms: round-robin, least-connections, and ip-hash. Health checks run at the configured interval and remove backends that fail the unhealthy threshold. They come back when they pass the healthy threshold. No external health checker needed.
UDP, QUIC, and HTTP/3
SmartProxy isn't TCP-only. Set transport: 'udp' in a route match and you're listening for datagrams. Pair it with a datagramHandler and you can implement any UDP protocol in TypeScript:
const udpHandler: TDatagramHandler = (datagram, info, reply) => {
console.log(`UDP from ${info.sourceIp}:${info.sourcePort}`);
reply(datagram); // Echo it back
};
const proxy = new SmartProxy({
routes: [{
name: 'udp-echo',
match: { ports: 5353, transport: 'udp' },
action: {
type: 'socket-handler',
datagramHandler: udpHandler,
udp: {
sessionTimeout: 60000,
maxSessionsPerIP: 100,
maxDatagramSize: 65535
}
}
}]
});
For QUIC and HTTP/3, set protocol: 'quic' and configure TLS (QUIC requires TLS 1.3). SmartProxy can receive QUIC on the frontend and translate to TCP
@push.rocks/smartvpn tunnels all VPN traffic inside a standard WebSocket connection over HTTPS. To any firewall, DPI box, or network observer, it looks indistinguishable from ordinary web traffic (the same kind of connection a chat app or real-time dashboard would open on port 443). It passes through Cloudflare, corporate proxies, and restrictive networks that block traditional VPN protocols on sight. Under the hood, smartvpn splits the work between a TypeScript control plane for configuration, client management, and telemetry, and a Rust data plane that handles encryption, tunneling, QoS, and packet classification at native speed. The two halves talk over typed JSON-lines IPC. Neither side compromises.
The Architecture in 30 Seconds
Your TypeScript code instantiates a VpnClient or VpnServer. Under the hood, smartvpn uses @push.rocks/smartrust to spawn a Rust binary (smartvpn_daemon) and opens a bidirectional JSON channel to it, either over stdio for development or a Unix socket for production. Every method call on the TypeScript side becomes a JSON command to the Rust daemon. Every response is typed end to end.
The Rust daemon handles everything that needs to be fast: Noise NK handshakes, XChaCha20-Poly1305 encryption with random 24-byte nonces, TUN device I/O, a three-tier priority queue (ICMP/DNS/SSH at the top, bulk flows at the bottom), per-client token-bucket rate limiting, adaptive keepalives, and automatic path MTU calculation. The TypeScript side never touches a raw packet. The Rust side never parses a config file.
Ecosystem note. @push.rocks/smartrust is the runtime side of the TypeScript/Rust bridge. smartvpn uses it so callers work with VpnClient and VpnServer while a Rust daemon handles privileged packet and crypto work.
What the TypeScript Looks Like
Connecting as a client is about what you would expect from a well-designed Node library:
import { VpnClient } from '@push.rocks/smartvpn';
const client = new VpnClient({
transport: { transport: 'stdio' },
});
await client.start();
const { assignedIp } = await client.connect({
serverUrl: 'wss://vpn.example.com/tunnel',
serverPublicKey: 'BASE64_SERVER_PUBLIC_KEY',
dns: ['1.1.1.1', '8.8.8.8'],
mtu: 1420,
});
console.log(`Connected: ${assignedIp}`);
That's it. Behind that await, the TypeScript layer spawned a Rust daemon, negotiated a Noise NK handshake with the server, stood up a TUN interface, and started routing encrypted traffic through a WebSocket tunnel. The transport choice (WebSocket over HTTPS) is deliberate. It passes through Cloudflare, corporate proxies, and anything else that terminates TLS. Most WireGuard setups choke in these environments. smartvpn doesn't.
Once connected, you get real-time telemetry for free. Call client.getConnectionQuality() and you get back smoothed RTT, jitter, loss ratio, and a link health classification (healthy / degraded / critical) — all measured by the daemon's own adaptive keepalive system, not by WebSocket pings that Cloudflare would swallow.
Easy Serverside Control over Clients
The server API (via VpnServer class) gives you a typed control surface over every connected client, and you can change anything at runtime without dropping connections.
You can list clients, pull per-client telemetry (bytes in/out, packets dropped, keepalive history), set individual rate limits with server.setClientRateLimit(), and kick anyone with server.disconnectClient(). Rate limits use a token-bucket algorithm at byte granularity in the Rust daemon. When you call setClientRateLimit('client-id', 5_000_000, 10_000_000), the bucket parameters change in the Rust process instantly — no reconnection, no config reload, no restart. Most VPN stacks need custom C extensions for this kind of runtime control.
KeepAlive and Adaptive HealthChecks
If you tunnel over WebSocket through a reverse proxy like Cloudflare, your WebSocket-level pings get swallowed. The proxy responds on behalf of the server, so your client thinks the link is healthy when the backend might be dead. smartvpn solves this with application-level keepalives — custom ping/pong frames inside the encrypted tunnel carrying 8-byte timestamps for precise RTT measurement.
The interval adapts. The daemon runs a three-state finite state machine: healthy (60s interval), degraded (30s), and critical (10s). Transitions have hysteresis — you need three consecutive good checks to upgrade from degraded to healthy, two bad ones to downgrade, which prevents the flapping that wastes bandwidth on unstable mobile connections. Dead peer detection fires after three consecutive timeouts in critical state. The whole thing runs inside the Rust process with zero allocations in steady state.
Quality of Service
The Rust data plane classifies every decrypted IP packet into three priority tiers by inspecting headers — no deep packet inspection, just port numbers and packet size. ICMP, DNS (port 53), SSH (port 22), and small packets under 128 bytes get high priority. Bulk flows that exceed 1 MB within a 60-second window get demoted to low. Everything else is normal. The priority channels drain with biased scheduling: high-priority packets always go first.
Under backpressure, the drop policy is equally deliberate: low-priority packets get dropped silently, normal packets get dropped next, and high-priority packets wait up to 5ms before being dropped as a last resort. Drop statistics are tracked per priority level and exposed through the telemetry API. This means your SSH session stays snappy even when someone on the same VPN is saturating the link with a large download.
Crypto Choices
The Noise NK handshake pattern means the client knows the server's static public key upfront but doesn't authenticate itself — the same trust model as connecting to a website via TLS. Two DH operations (ephemeral-static, then ephemeral-ephemeral) produce forward-secret transport keys. Post-handshake, everything goes through XChaCha20-Poly1305 with random 24-byte nonces. The large nonce space means you never need counter synchronization between peers; just generate a random nonce per packet. The wire format is minimal: [nonce:24B][ciphertext][tag:16B], inside a binary frame of [type:1B][length:4B][payload].
The MTU Math
The daemon also calculates tunnel overhead precisely. IP header (20 bytes) + TCP with timestamps (32 bytes) + WebSocket framing (6 bytes) + VPN frame header (5 bytes) + Noise AEAD tag (16 bytes) = 79 bytes of overhead. On a standard 1500-byte Ethernet link, that gives you an effective TUN MTU of 1421 bytes. The default of 1420 is conservative and correct. Oversized packets get an ICMP Fragmentation Needed written
smartproxy A proxy built for handling high workloads — now internally powered by Rust for maximum performance.
smartproxy retains the exact same TypeScript interface as before, but uses Rust under the hood for all network operations.
Ecosystem note. The recurring Rust move in foss.global packages is to keep the TypeScript surface stable and shift workload-heavy internals into Rust where native networking or OS access matters. smartproxy is an early example of that boundary.
We'll continue optimizing foss.global products and further leverage Rust across the ecosystem where it makes a real difference — without sacrificing the ease of use you get from TypeScript-based packages.
Happy coding.
The foss.global team.
In the world of TypeScript development, finding the right database tools that combine type safety with powerful features can be challenging. As applications grow in complexity, particularly in distributed environments, the need for robust data management becomes ever more critical. This is where @push.rocks/smartdata enters the picture - a TypeScript-first MongoDB wrapper designed to simplify complex data operations while providing powerful features for modern application development.
What is @push.rocks/smartdata?
@push.rocks/smartdata is a comprehensive MongoDB wrapper that provides strong TypeScript integration, offering type-safe operations, automated schema handling, and advanced features for distributed systems. Created by the foss.global team (which I am a part of), this library aims to simplify database interactions while adding powerful features that extend MongoDB's capabilities.
Ecosystem note. push.rocks is the shared library namespace in the foss.global stack. smartdata is a MongoDB-backed data layer for TypeScript services, not a hosted database product.
Key Features That Set It Apart
What makes @push.rocks/smartdata stand out from other MongoDB wrappers? Let's explore its most distinctive features:
1. True TypeScript-First Approach
Unlike many libraries that add TypeScript as an afterthought, @push.rocks/smartdata is built from the ground up with TypeScript in mind. This provides several advantages:
- Decorator-Based Schema Definition: Define your MongoDB schemas using TypeScript decorators
- Type-Safe CRUD Operations: Eliminate runtime errors with compile-time type checking
- Deep Query Type Safety: Fully type-safe queries for nested object properties via
DeepQuery<T>
2. Advanced Search Capabilities
The library implements a powerful and intuitive search system that goes beyond basic queries:
// Define a model with searchable fields
@Collection(() => db)
class Product extends SmartDataDbDoc<Product, Product> {
@unI() public id: string = 'product-id';
@svDb() @searchable() public name: string;
@svDb() @searchable() public description: string;
@svDb() @searchable() public category: string;
@svDb() public price: number;
}
// Advanced search options
await Product.search('"Kindle Paperwhite"'); // Exact phrase across all fields
await Product.search('Air*'); // Wildcard search
await Product.search('name:Air*'); // Field-scoped wildcard
await Product.search('category:Electronics AND name:iPhone'); // Boolean operators
await Product.search('(Furniture OR Electronics) AND Chair'); // Grouping
This search functionality supports Lucene-style queries with exact matches, wildcards, field-scoping, boolean operators, and more - all while maintaining type safety.
3. EasyStore for Simple Key-Value Storage
For simpler data needs, @push.rocks/smartdata offers EasyStore - a type-safe key-value storage system with automatic persistence:
interface ConfigStore {
apiKey: string;
settings: {
theme: string;
notifications: boolean;
};
}
// Create a type-safe EasyStore
const store = await db.createEasyStore<ConfigStore>('app-config');
// Write and read with full type safety
await store.writeKey('apiKey', 'secret-api-key-123');
await store.writeKey('settings', { theme: 'dark', notifications: true });
const apiKey = await store.readKey('apiKey'); // Type: string
const settings = await store.readKey('settings'); // Type: { theme: string, notifications: boolean }
4. Built-in Distributed Coordination
One of the most powerful features is the built-in support for distributed systems:
// Create a distributed coordinator
const coordinator = new SmartdataDistributedCoordinator(db);
// Start coordination
await coordinator.start();
// Handle leadership changes
coordinator.on('leadershipChange', (isLeader) => {
if (isLeader) {
// This instance is now the leader
startPeriodicJobs();
} else {
// This instance is no longer the leader
stopPeriodicJobs();
}
});
// Execute tasks only on the leader
await coordinator.executeIfLeader(async () => {
// This code only runs on the leader instance
await runImportantTask();
});
This makes it significantly easier to build reliable distributed applications with leader election, task coordination, and more.
5. Real-Time Data Synchronization
For applications requiring real-time updates, @push.rocks/smartdata provides watchers with RxJS integration:
// Create a watcher for active users
const watcher = await User.watch(
{ active: true },
{ fullDocument: true, bufferTimeMs: 100 }
);
// Subscribe to changes using RxJS
watcher.changeSubject.subscribe((change) => {
console.log('Change operation:', change.operationType);
console.log('Document changed:', change.docInstance);
// Handle different operations
if (change.operationType === 'insert') {
notifyNewUser(change.docInstance);
}
});
This simplifies building reactive applications that respond to data changes in real-time.
Practical Implementation
Let's walk through a practical implementation to see how @push.rocks/smartdata can be used in a real-world scenario.
Setting Up Your Database Connection
First, establish a connection to your MongoDB database:
import { SmartdataDb } from '@push.rocks/smartdata';
// Create a database instance
const db = new SmartdataDb({
mongoDbUrl: 'mongodb://localhost:27017/myapp',
mongoDbName: 'myapp',
mongoDbUser: 'username',
mongoDbPass: 'password',
});
// Initialize and connect
await db.init();
Defining Your Data Models
Next, define your data models using TypeScript classes with decorators:
import {
SmartDataDbDoc,
Collection,
unI,
svDb,
index,
searchable,
} from '@push.rocks/smartdata';
import { ObjectId } from 'mongodb';
@Collection(() => db)
class User extends SmartDataDbDoc<User, User> {
@unI()
public id: string = 'user-' + Math.random().toString(36).substring(2, 9);
@svDb()
@searchable()
public username: string;
@svDb()
@searchable()
@index()
public email: string;
@svDb()
public organizationId: ObjectId; // Stored as BSON ObjectId
@svDb()
public profilePicture: Buffer; // Stored as BSON Binary
@svDb({
serialize: (data) => JSON.stringify(data),
deserialize: (data) => JSON.parse(data),
})
public preferences: Record<string, any>;
constructor(username: string, email: string) {
super();
this.username = username;
this.email = email;
}
}
Performing CRUD Operations
With your models defined, you can now perform fully type-safe CRUD operations:
// Create a user
const user = new User('johndoe', 'john@example.com');
user.preferences = { theme: 'dark', notifications: true };
await user.save();
// Retrieve users
const singleUser = await User.getInstance({ username: 'johndoe' });
const users = await User.getInstances({ email: /example\.com$/ });
// Update a user
singleUser.email = 'john.doe@example.com';
await singleUser.save();
// Upsert operation
const upsertedUser = await User.upsert(
{ username: 'janedoe' },
{ email: 'jane@example.com', preferences: { theme: 'light' } }
);
// Delete a user
await singleUser.delete();
Using Advanced Features
For more complex scenarios, leverage the advanced features:
// Using transactions for atomic operations
const session = db.startSession();
try {
await session.withTransaction(async () => {
const sender = await User.getInstance({ id: 'user1' }, session);
const recipient = await User.getInstance({ id: 'user2' }, session);
// Transfer credits atomically
sender.credits -= 100;
recipient.credits += 100;
await sender.save({ session });
await recipient.save({ session });
});
} finally {
await session.endSession();
}
// Implementing document lifecycle hooks
@Collection(() => db)
class Order extends SmartDataDbDoc<Order, Order> {
@unI()
public id: string = 'order-' + Date.now();
@svDb()
public items: string[];
@svDb()
public total: number = 0;
// Hook called before saving
async beforeSave() {
// Calculate total from