@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
214 lines • 8.43 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. =========
/**
* One bash variable attribute, spelled as its `declare` letter.
*
* Declaration order is the order `declare -p` prints a cluster in,
* pinned exhaustively against bash 5.2.37 over all 72 ordered pairs of
* `a A i l n r t u x`: `a`/`A` first, then `i n r t x`, then `l`/`u`. It
* is bash's own internal order, not the order the letters were typed in
* (`declare -xri` prints `-irx`), and not alphabetical.
*
* `-a` and `-A` are deliberately absent: whether a variable is an
* indexed array, an associative array or a scalar is what its value
* *is*, so storing it a second time as an attribute would let the two
* contradict each other. `attrLetters` derives them from the value.
*/
export var VarAttr;
(function (VarAttr) {
VarAttr["Integer"] = "i";
VarAttr["Nameref"] = "n";
VarAttr["Readonly"] = "r";
VarAttr["Trace"] = "t";
VarAttr["Export"] = "x";
VarAttr["Lower"] = "l";
VarAttr["Upper"] = "u";
})(VarAttr || (VarAttr = {}));
// `declare -p` print order, derived from the enum rather than restated:
// a string enum has no reverse mapping, so `Object.values` is its
// declaration order exactly. Python reads the same order off `VarAttr`
// itself, so an attribute added to one enum cannot print in a different
// place from the other, and cannot be silently dropped by a hand-written
// list nobody remembered to extend.
const ATTR_ORDER = Object.values(VarAttr);
/** What a variable's value is, derived from the value itself. */
export var VarKind;
(function (VarKind) {
VarKind["Scalar"] = "scalar";
VarKind["Indexed"] = "indexed";
VarKind["Assoc"] = "assoc";
})(VarKind || (VarKind = {}));
/**
* The records a prefix assignment shadowed for one function call.
*
* bash runs `x=1 f` with `x` in a scope of its own around the function,
* its temporary environment: the caller's value comes back when the call
* returns, `unset x` inside reveals it, and an `export` or `readonly` of
* the name lets the value outlive the call. A frame of this type on the
* call path is that scope; a plain frame holds a function's `local`
* shadows.
*/
export class TempEnv extends Map {
}
const NO_ATTRS = new Set();
/** Build a variable record. */
export function makeVar(value = null, attrs = NO_ATTRS) {
return { value, attrs };
}
/**
* What kind of variable this is, read off its value.
*
* An unset variable reads as a scalar: bash renders `declare -i n` with
* no `-a`, so nothing but an actual array value earns the letter.
*/
export function varKind(v) {
if (Array.isArray(v.value))
return VarKind.Indexed;
if (v.value !== null && typeof v.value === 'object')
return VarKind.Assoc;
return VarKind.Scalar;
}
/** Copy a variable and its mutable array value for a child shell. */
export function copyVar(v) {
if (Array.isArray(v.value))
return withValue(v, [...v.value]);
if (v.value !== null && typeof v.value === 'object')
return withValue(v, { ...v.value });
return v;
}
/**
* The variable with a new value and the same attributes, but a reference's
* under an array: bash's reference cannot hold one, so `declare -n r;
* r=(x)` leaves `declare -a r`.
*/
export function withValue(v, value) {
if (value !== null && typeof value === 'object' && v.attrs.has(VarAttr.Nameref)) {
return { ...v, value, attrs: new Set([...v.attrs].filter((a) => a !== VarAttr.Nameref)) };
}
return { ...v, value };
}
/**
* The variable with its managed pointer dropped, value kept.
*
* An agent write to a managed name shadows session-locally: the record
* becomes a plain variable for this session only, so the fill step
* never clobbers it and the declaration (new sessions fetch fresh) and
* the remote store are untouched by construction.
*/
export function detach(v) {
return { value: v.value, attrs: v.attrs };
}
/**
* The variable with one attribute turned on or off. `+attr` is the off
* direction, which is why this takes a flag rather than being two
* functions.
*/
export function withAttr(v, attr, on = true) {
const attrs = new Set(v.attrs);
if (on)
attrs.add(attr);
else
attrs.delete(attr);
return { ...v, attrs };
}
/**
* The stored attribute letters, in `declare -p` print order.
*
* The tail of `attrLetters` without the `a`/`A` kind lead, which is
* derived rather than stored. Split out because two callers want the
* stored half alone: the serializer, which must not write a letter it
* would then read back as an attribute the value already implies, and
* `attrLetters` itself.
*/
/**
* Apply the value-shaping attributes to one scalar being stored.
*
* bash applies these at assignment, not at read: `declare -l s; s=ABC`
* stores `abc`, and `declare -i n; n=2+2` stores `4`, so `declare -p`
* and `$s` agree with no per-read work. Order is integer first, then
* case, which only matters for hex digits and is what GNU does
* (`declare -il n=0xA` stores `10`). `-l` and `-u` cannot both hold; a
* declaration that sets one clears the other, so at most one applies.
*/
export function coerceScalar(text, attrs, integer) {
let out = text;
if (attrs.has(VarAttr.Integer) && integer !== null)
out = integer(out);
if (attrs.has(VarAttr.Lower))
return out.toLowerCase();
if (attrs.has(VarAttr.Upper))
return out.toUpperCase();
return out;
}
/**
* `coerceScalar` lifted over every value shape. An array applies the
* attribute per element, which is GNU's `declare -ai a=(1+1 2*3)` giving
* `([0]="2" [1]="6")`. A write that assigns some elements only (`a[1]=x`,
* `a=x`) names them in `assigned`, and the others are carried over as
* stored: bash shapes a value when it is assigned, so `declare -i a;
* a[0]=9` never re-evaluates a stored `a[1]`.
*/
export function coerceValue(value, attrs, integer, assigned = null) {
if (!attrs.has(VarAttr.Integer) && !attrs.has(VarAttr.Lower) && !attrs.has(VarAttr.Upper)) {
return value;
}
if (typeof value === 'string')
return coerceScalar(value, attrs, integer);
if (Array.isArray(value)) {
return value.map((v, i) => v === null || (assigned !== null && !assigned.has(i)) ? v : coerceScalar(v, attrs, integer));
}
const out = {};
for (const [k, v] of Object.entries(value)) {
out[k] = assigned !== null && !assigned.has(k) ? v : coerceScalar(v, attrs, integer);
}
return out;
}
export function storedAttrs(v) {
return ATTR_ORDER.filter((a) => v.attrs.has(a)).join('');
}
/**
* The attribute set a stored letter cluster spells, the inverse of
* `storedAttrs`, used when a persisted session is read back. A letter
* that names no attribute is ignored rather than throwing: the store is
* shared with the other language and with future versions, and refusing
* to load a session because one letter is unknown loses far more than
* the letter.
*/
export function attrsFromLetters(letters) {
const known = new Set(ATTR_ORDER);
const out = new Set();
for (const c of letters.split('')) {
if (known.has(c))
out.add(c);
}
return out;
}
/**
* The attribute cluster `declare -p` prints for this variable.
*
* `-a`/`-A` come from the value's kind and lead, then the stored
* attributes in print order. bash prints `--` for a plain scalar with
* nothing set, which is the caller's to render since only it knows it is
* writing a `declare` line.
*/
export function attrLetters(v) {
const kind = varKind(v);
let lead = '';
if (kind === VarKind.Indexed)
lead = 'a';
else if (kind === VarKind.Assoc)
lead = 'A';
return lead + storedAttrs(v);
}
//# sourceMappingURL=variable.js.map