@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
112 lines • 4.96 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 { parseEventFilename } from "../../vfs/gcal/event_entry.js";
import { enoent } from "../../errors/fs.js";
import { makeRead } from "../hierarchy/read.js";
import { compactJsonBytes } from "../render/json.js";
import { dayBounds } from "./day.js";
import { bucketZone, calendarIndex, calendarPayload, scopedBucket, } from "./readdir.js";
import { detectScope } from "./scope.js";
async function readCalendarJson(accessor, match, path) {
const calendars = await calendarIndex(accessor);
const entry = calendars.get(match.slots.calendar ?? '');
if (entry === undefined)
throw enoent(path.virtual);
return calendarPayload(entry, bucketZone(accessor, calendars));
}
/**
* The calendar id, day and event id an event file names. Only the day the
* name is on is listed for it, so a read in a multi-day bucket costs what one
* in a day directory does. Mirrors Python's `_event_of`.
*/
function eventOf(accessor, calendars, match, path) {
const calId = calendars.get(match.slots.calendar ?? '')?.id;
if (typeof calId !== 'string')
throw enoent(path.virtual);
const days = scopedBucket(accessor, match.slots.bucket ?? '', bucketZone(accessor, calendars), path.virtual);
const [eventId, named] = parseEventFilename(match.slots.event ?? '');
// A name carries its day exactly when the mount's buckets span several.
if ((named !== null) !== accessor.config.bucketDays > 1)
throw enoent(path.virtual);
const day = named ?? days[0] ?? '';
if (!days.includes(day))
throw enoent(path.virtual);
return [calId, day, eventId];
}
/** The events one local day of a calendar holds. Mirrors Python's `_day_events`. */
async function dayEvents(accessor, calId, day, tz) {
const [timeMin, timeMax] = dayBounds(day, tz);
return accessor.calendar.listEvents(calId, timeMin, timeMax, tz, accessor.timeRange);
}
/**
* Read one event's raw API payload.
*
* The event file holds the events.list item unmodified: the directory name
* and the HHMM segment are a view, while the payload is the truth an
* absolute-instant comparison has to be made against.
*/
async function readEvent(accessor, match, path) {
const calendars = await calendarIndex(accessor);
const [calId, day, eventId] = eventOf(accessor, calendars, match, path);
for (const event of await dayEvents(accessor, calId, day, bucketZone(accessor, calendars))) {
if (event.id === eventId)
return compactJsonBytes(event);
}
throw enoent(path.virtual);
}
/**
* Read several event files, listing each day they name once. A day's listing
* answers every event asked of it, so a batch of one day's events costs what
* one read does. A path that is not an event file, or names an event its day
* does not hold, is left out for its own read to answer. Mirrors Python's
* `read_many`.
*/
export async function readMany(accessor, paths, limit) {
const calendars = await calendarIndex(accessor);
const tz = bucketZone(accessor, calendars);
const days = new Map();
for (const path of paths) {
const match = detectScope(path);
if (match.kind !== 'event')
continue;
let named;
try {
named = eventOf(accessor, calendars, match, path);
}
catch (err) {
console.debug(`readMany leaves out ${path.virtual}: ${String(err)}`);
continue;
}
const [calId, day, eventId] = named;
const key = JSON.stringify([calId, day]);
const events = days.get(key) ?? [calId, day, new Map()];
events[2].set(eventId, path.virtual);
days.set(key, events);
}
const answers = new Map();
for (const [calId, day, events] of days.values()) {
for (const event of await dayEvents(accessor, calId, day, tz)) {
const virtual = typeof event.id === 'string' ? events.get(event.id) : undefined;
const body = compactJsonBytes(event);
if (virtual !== undefined && body.byteLength <= limit)
answers.set(virtual, body);
}
}
return answers;
}
export const read = makeRead(detectScope, {
calendar_json: readCalendarJson,
event: readEvent,
});
//# sourceMappingURL=read.js.map