@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
240 lines • 9.55 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { ManualClock, SystemClock } from "./clock.js";
const MAX_SEED = Number.MAX_SAFE_INTEGER;
const WORD_BITS = 32;
const WORD_MASK = 0xffffffff;
const WORDS_AHEAD = 1024;
const TIMESTAMP_BITS = 48n;
const TIMESTAMP_MASK = (1n << TIMESTAMP_BITS) - 1n;
// rand_a carries the intra-millisecond counter (RFC 9562 section 6.2), seeded
// well below its maximum so a burst has room to climb, as ids.py does.
const COUNTER_MAX = 0xfff;
const COUNTER_SEED_BITS = 8;
const RANDOM_BITS = 62;
const VERSION = 7n;
const VARIANT = 2n;
const SPLITMIX_GAMMA = 0x9e3779b97f4a7c15n;
const SPLITMIX_MUL1 = 0xbf58476d1ce4e5b9n;
const SPLITMIX_MUL2 = 0x94d049bb133111ebn;
const UINT64_MASK = (1n << 64n) - 1n;
/**
* The millisecond and counter of the next id after `lastMs`. Two ids minted in
* the same millisecond are ordered by the counter. A millisecond whose counter
* is exhausted borrows the next one: on a clock that stands still or went back,
* waiting would never end. `_stamp` in ids.py is the twin and says why.
*/
function stamp(lastMs, counter, nowMs, fresh) {
if (nowMs > lastMs)
return [nowMs, fresh()];
if (counter < COUNTER_MAX)
return [lastMs, counter + 1];
return [lastMs + 1, fresh()];
}
/** Lay out a UUIDv7 in canonical lowercase hyphenated form. */
function layout(timestampMs, counter, high, low) {
const value = ((BigInt(timestampMs) & TIMESTAMP_MASK) << 80n) |
(VERSION << 76n) |
(BigInt(counter) << 64n) |
(VARIANT << 62n) |
(BigInt(high) << BigInt(WORD_BITS)) |
BigInt(low);
const digits = value.toString(16).padStart(32, '0');
return [
digits.slice(0, 8),
digits.slice(8, 12),
digits.slice(12, 16),
digits.slice(16, 20),
digits.slice(20),
].join('-');
}
/**
* Mint an RFC 9562 UUIDv7 in canonical lowercase hyphenated form.
*
* The first 48 bits are a unix-millisecond timestamp, so ids sort by
* creation time and stay index-friendly as database primary keys; the
* next 12 bits order ids minted inside one millisecond, and the last 62
* bits are random. Stamped by the process's own `SystemIdSource`.
*/
export function uuid7() {
return SYSTEM_IDS.uuid7();
}
/** Mint a fresh workspace id (UUIDv7). */
export function newWorkspaceId() {
return uuid7();
}
/** Mint a fresh session id (UUIDv7). */
export function newSessionId() {
return uuid7();
}
const SYSTEM_FLOOR = { lastMs: -1, counter: 0 };
/**
* Platform randomness used when no source is supplied. A UUIDv7 is stamped
* from `clock` and ordered inside one millisecond by a counter, as
* `SeededIdSource` orders its ids. Every source on the system clock shares the
* process's floor, so ids minted anywhere in the process sort by when they
* were made. A source on another clock keeps a floor of its own, so a manual
* clock never shares one with the system clock.
*/
export class SystemIdSource {
clock;
floor;
/** @param clock the clock ids are stamped from; omitted means the system clock. */
constructor(clock) {
this.clock = clock ?? new SystemClock();
this.floor = this.clock instanceof SystemClock ? SYSTEM_FLOOR : { lastMs: -1, counter: 0 };
}
/** A fresh UUIDv7 stamped from the source's clock. */
uuid7() {
const floor = this.floor;
const [ms, counter] = stamp(floor.lastMs, floor.counter, Math.floor(this.clock.now() * 1000), () => cryptoU32() >>> (WORD_BITS - COUNTER_SEED_BITS));
floor.lastMs = ms;
floor.counter = counter;
const high = cryptoU32() >>> (2 * WORD_BITS - RANDOM_BITS);
return layout(ms, counter, high, cryptoU32());
}
/** A uniform 32-bit integer from the platform generator. */
randomU32() {
return Math.floor(Math.random() * 2 ** WORD_BITS);
}
/**
* Twice `count` platform words, and at least `WORDS_AHEAD`, each as four
* little-endian bytes: a caller drawing below a limit, which redraws up to
* half its words, seldom has to ask again.
*/
randomWords(count) {
count = Math.max(2 * count, WORDS_AHEAD);
const bytes = new Uint8Array(count * 4);
const view = new DataView(bytes.buffer);
for (let at = 0; at < count; at++)
view.setUint32(at * 4, this.randomU32(), true);
return bytes;
}
}
const SYSTEM_IDS = new SystemIdSource();
function cryptoU32() {
return globalThis.crypto.getRandomValues(new Uint32Array(1))[0] ?? 0;
}
function splitmix64(state) {
const next = (state + SPLITMIX_GAMMA) & UINT64_MASK;
let z = next;
z = ((z ^ (z >> 30n)) * SPLITMIX_MUL1) & UINT64_MASK;
z = ((z ^ (z >> 27n)) * SPLITMIX_MUL2) & UINT64_MASK;
return [next, z ^ (z >> 31n)];
}
function rotl(value, shift) {
return ((value << shift) | (value >>> (32 - shift))) >>> 0;
}
/**
* Ids and draws replayed exactly from a seed.
*
* Draws come from xoshiro128** (Blackman and Vigna) seeded through
* SplitMix64, so one seed yields one sequence on every host and in both
* languages. A UUIDv7 is stamped from `clock`, ordered inside one
* millisecond by a counter, and borrows the next millisecond rather than
* waiting when that counter runs out.
*/
export class SeededIdSource {
seed;
clock;
state;
lastMs = -1;
counter = 0;
/**
* @param seed the sequence to replay, from 0 to `2**53 - 1`.
* @param clock the clock ids are stamped from; omitted means a
* `ManualClock` at zero, so ids depend on the seed alone.
*/
constructor(seed, clock) {
this.seed = seed;
if (!Number.isSafeInteger(seed) || seed < 0 || seed > MAX_SEED) {
throw new RangeError(`seed must be in [0, 2**53 - 1]: ${String(seed)}`);
}
this.clock = clock ?? new ManualClock();
const [state, first] = splitmix64(BigInt(seed));
const [, second] = splitmix64(state);
this.state = Uint32Array.of(Number(first >> BigInt(WORD_BITS)), Number(first & BigInt(WORD_MASK)), Number(second >> BigInt(WORD_BITS)), Number(second & BigInt(WORD_MASK)));
}
/** The next 32-bit draw of the seeded sequence. */
randomU32() {
const s = this.state;
const s0 = s[0] ?? 0;
const s1 = s[1] ?? 0;
const s2 = (s[2] ?? 0) ^ s0;
const s3 = (s[3] ?? 0) ^ s1;
s[0] = s0 ^ s3;
s[1] = s1 ^ s2;
s[2] = s2 ^ (s1 << 9);
s[3] = rotl(s3 >>> 0, 11);
return Math.imul(rotl(Math.imul(s1, 5) >>> 0, 7), 9) >>> 0;
}
/** The next `count` draws of the seeded sequence, each as four little-endian bytes. */
randomWords(count) {
const bytes = new Uint8Array(count * 4);
const view = new DataView(bytes.buffer);
for (let at = 0; at < count; at++)
view.setUint32(at * 4, this.randomU32(), true);
return bytes;
}
/** The next UUIDv7 of the seeded sequence, stamped from the clock. */
uuid7() {
const [ms, counter] = stamp(this.lastMs, this.counter, Math.floor(this.clock.now() * 1000), () => this.randomU32() >>> (WORD_BITS - COUNTER_SEED_BITS));
this.lastMs = ms;
this.counter = counter;
const high = this.randomU32() >>> (2 * WORD_BITS - RANDOM_BITS);
return layout(this.lastMs, this.counter, high, this.randomU32());
}
}
/**
* A uniform bigint in `[0, limit)` drawn from `ids`.
*
* Whole 32-bit words are drawn, most significant first, the top bits of the
* limit's own bit length kept, and a value past the limit drawn again, which
* discards at most half the draws. `random_below` in ids.py is the twin and
* draws the same words for the same limit.
*/
export function randomBelowBig(ids, limit) {
if (limit <= 0n)
throw new RangeError(`limit must be positive: ${String(limit)}`);
const bits = limit.toString(2).length;
const words = Math.ceil(bits / WORD_BITS);
const shift = BigInt(words * WORD_BITS - bits);
for (;;) {
let drawn = 0n;
for (let i = 0; i < words; i++) {
drawn = (drawn << BigInt(WORD_BITS)) | BigInt(ids.randomU32());
}
const value = drawn >> shift;
if (value < limit)
return value;
}
}
/**
* A uniform integer in `[0, limit)` drawn from `ids`, as `randomBelowBig`
* draws it; a limit within one word skips the bigint arithmetic.
*/
export function randomBelow(ids, limit) {
if (!Number.isSafeInteger(limit) || limit <= 0) {
throw new RangeError(`limit must be a positive safe integer: ${String(limit)}`);
}
if (limit > WORD_MASK)
return Number(randomBelowBig(ids, BigInt(limit)));
const shift = Math.clz32(limit);
for (;;) {
const value = Math.floor(ids.randomU32() / 2 ** shift);
if (value < limit)
return value;
}
}
//# sourceMappingURL=ids.js.map