UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

112 lines • 4.96 kB
// ========= 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