Scripting
varc module reference
Use binary-safe keyspace, stream, time, diff, byte, and deterministic fixture helpers designed for Redis and Valkey workflows.
The varc: namespace contains application-native modules for Redis and Valkey data. They are pure, embedded, and available to every execution class.
Strings passed to byte-oriented functions are encoded as UTF-8. Uint8Array and ArrayBuffer inputs are copied, so the helpers do not retain a mutable view into the caller’s buffer.
| Module | Use it for | Version | Available in | License | Upstream |
|---|---|---|---|---|---|
| varc:bytes | Convert, concatenate, and compare binary-safe values. | 1.0.0 | Scripts, Macros, Transformers, Dashboards | UNLICENSED | Project site |
| varc:diff | Describe byte, line, and JSON changes with bounded data structures. | 1.0.0 | Scripts, Macros, Transformers, Dashboards | UNLICENSED | Project site |
| varc:keyspace | Split, join, inspect, and Redis-glob-match binary keys. | 1.0.0 | Scripts, Macros, Transformers, Dashboards | UNLICENSED | Project site |
| varc:seed | Generate deterministic fixture data from a repeatable seed. | 1.0.0 | Scripts, Macros, Transformers, Dashboards | UNLICENSED | Project site |
| varc:streams | Parse and compare Redis stream IDs without losing integer precision. | 1.0.0 | Scripts, Macros, Transformers, Dashboards | UNLICENSED | Project site |
| varc:time | Decode Redis TTL replies and parse or format compact durations. | 1.0.0 | Scripts, Macros, Transformers, Dashboards | UNLICENSED | Project site |
Bytes
Import from varc:bytes:
import { from, toString, concat, compare, equals } from "varc:bytes";
All functions accept BytesLike = Uint8Array | ArrayBuffer | string.
| Function | Return | Behavior |
|---|---|---|
from(value) |
Uint8Array |
Encode a string as UTF-8 or copy a byte/buffer input. |
toString(value) |
string |
Decode the bytes as UTF-8 text. |
concat(...values) |
Uint8Array |
Join values without a separator. |
compare(left, right) |
-1, 0, or 1 |
Compare unsigned bytes lexicographically, then length. |
equals(left, right) |
boolean |
Test byte-for-byte equality. |
import { concat, equals, toString } from "varc:bytes";
const key = concat("customer", new Uint8Array([58]), "42");
return { text: toString(key), matches: equals(key, "customer:42") };
toString is for data known to represent text. Use base64 or hex when arbitrary bytes must round-trip through a string.
Diff
Import from varc:diff:
import { diffBytes, diffLines, diffJson } from "varc:diff";
| Function | Return |
|---|---|
diffBytes(before, after) |
{ offset, before, after }[] |
diffLines(before, after) |
{ kind: "equal" | "add" | "remove", line }[] |
diffJson(before, after, path?) |
{ path, before?, after? }[] |
diffBytes reports only changed offsets. A missing byte at either end is represented by null. Equal inputs return an empty array.
diffLines splits on newline characters and uses a stable longest-common-subsequence diff. It retains equal lines so a renderer can reconstruct both sides.
diffJson recursively compares arrays and objects, sorts keys for stable output, and uses escaped JSON Pointer paths. Primitive or type changes are reported at the current path; the root path is /.
import { diffJson } from "varc:diff";
return diffJson({ profile: { name: "Ada", enabled: false } }, { profile: { name: "Ada", enabled: true } });
// [{ path: "/profile/enabled", before: false, after: true }]
These helpers build their result in memory. Keep inputs comfortably within the execution class’s memory and output limits.
Keyspace
Import from varc:keyspace:
import { equals, split, join, prefixes, matches } from "varc:keyspace";
The default separator is ":", but every byte-oriented function accepts a string, Uint8Array, or ArrayBuffer separator.
| Function | Return | Behavior |
|---|---|---|
equals(left, right) |
boolean |
Byte-for-byte equality. |
split(key, separator?) |
Uint8Array[] |
Split without decoding the key; empty sections are preserved. |
join(parts, separator?) |
Uint8Array |
Join binary-safe parts. |
prefixes(key, separator?) |
Uint8Array[] |
Return each cumulative hierarchy prefix, including the full key. |
matches(pattern, key) |
boolean |
Apply Redis-style glob matching to bytes. |
An empty separator passed to split or prefixes throws TypeError. matches supports *, ?, character classes, ranges, negated classes, and backslash escapes.
import { prefixes, matches } from "varc:keyspace";
import { toString } from "varc:bytes";
const key = new TextEncoder().encode("customer:42:profile");
return {
matches: matches("customer:*:profile", key),
parents: prefixes(key).map(toString),
};
Seed
createSeededRandom(seed) from varc:seed creates a deterministic generator:
import { createSeededRandom } from "varc:seed";
const random = createSeededRandom("invoice-fixture-v1");
return {
id: random.uuid(),
attempt: random.int(1, 5),
score: random.float(),
token: random.bytes(8),
region: random.pick(["eu-west", "us-east", "ap-south"]),
order: random.shuffle([1, 2, 3, 4]),
};
| Method | Behavior |
|---|---|
float() |
Return a value in [0, 1). |
int(min, max) |
Return an inclusive safe integer between min and max. |
bytes(length) |
Return deterministic bytes for a non-negative safe length. |
pick(values) |
Return one item; an empty list throws RangeError. |
shuffle(values) |
Return a shuffled copy without mutating the input. |
uuid() |
Return an RFC 4122 version-4-shaped deterministic UUID. |
The seed is SHA-256-derived and the sequence uses xoshiro128**. This is for repeatable fixtures and demonstrations, not passwords, tokens, signatures, or any other security decision. Use randomBytes or crypto.getRandomValues when unpredictability is required.
Streams
Import Redis stream ID helpers from varc:streams:
import { parseId, formatId, compareIds, nextId, previousId, isAutoId } from "varc:streams";
interface StreamId {
milliseconds: bigint;
sequence: bigint;
}
| Function | Return | Behavior |
|---|---|---|
parseId(value) |
StreamId |
Parse an exact milliseconds-sequence ID. |
formatId(value) |
string |
Format two unsigned 64-bit parts. |
compareIds(left, right) |
-1, 0, or 1 |
Compare by milliseconds, then sequence. |
nextId(value) |
string |
Return the immediate unsigned 64-bit successor. |
previousId(value) |
string | null |
Return the predecessor, or null for 0-0. |
isAutoId(value) |
boolean |
Recognize * and valid milliseconds-* forms. |
Parsing rejects malformed IDs and parts outside the unsigned 64-bit range. nextId throws when both components are already at their maximum.
import { compareIds, nextId, parseId } from "varc:streams";
const start = parseId("1710000000000-42");
return {
after: nextId(start),
ordered: compareIds(start, "1710000000001-0") < 0,
};
Time
Import from varc:time:
import { decodeTtl, parseDuration, formatDuration } from "varc:time";
decodeTtl(milliseconds) turns a Redis PTTL reply into a discriminated union:
type TtlState = { kind: "missing" } | { kind: "persistent" } | { kind: "expires"; milliseconds: number };
-2 becomes missing, -1 becomes persistent, and a non-negative safe integer becomes expires. Any other input throws RangeError.
parseDuration(value) accepts plain milliseconds or combined ms, s, m, h, d, and w units. formatDuration(milliseconds) returns the matching compact whole-unit form.
import { decodeTtl, formatDuration, parseDuration } from "varc:time";
const ttl = decodeTtl(await redis.pttl("session:42"));
return {
ttl,
retryAfter: parseDuration("1m 30s"),
retention: formatDuration(7 * 24 * 60 * 60 * 1000),
};
Durations must resolve to non-negative safe integer milliseconds. Invalid text, negative values, and values outside the safe range fail explicitly.