UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

106 lines (104 loc) • 4.63 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 { AirtableAccessor } from "../../accessor/airtable.js"; import { AirtableConfig } from "../../core/airtable/config.js"; import { buildDeltaHook } from "../../core/airtable/events.js"; import { firstLines as airtableFirstLines, readStream as airtableStream, } from "../../core/airtable/read.js"; import { readdir as airtableReaddir } from "../../core/airtable/readdir.js"; import { linesContaining as airtableLinesContaining } from "../../core/airtable/search.js"; import { stat as airtableStat } from "../../core/airtable/stat.js"; import { VFSName } from "../../types.js"; import { BaseVFS } from "../base.js"; export const VFS_MD = `\`\`\`text {prefix} bases/ <base-name>__<base-id>/ base.json # base id, name, permission, tables <table-name>__<table-id>/ table.json # typed fields (with options), views records.jsonl # one record per line views/ <view-name>__<view-id>.jsonl # records the view shows, in order \`\`\` - \`ls\` a directory first to find exact names: the name part of a segment is sanitized, the id after the last \`__\` is exact. - A \`records.jsonl\` line is a normalized record: \`record_id\`, \`created_time\` and \`fields\`, keyed by field name with values as Airtable returns them. \`table.json\` gives each field's type. - Airtable omits empty cells, so a missing key means empty, a \`false\` checkbox included. A link field is a list of record ids; an attachment URL expires two hours after it was read. - \`records.jsonl\` lists in the API's own order; a view file applies the view's filter and sort. - A file over \`max_read_records\` records stops with \`File too large\` after that many, while \`head -n N\` fetches just N records. - The \`airtable\` CLI, if installed, reaches what a file cannot: server-side filters (\`airtable record list --formula\` or \`--view\`), one record by id (\`airtable record get\`) and comments (\`airtable comment list\`). \`\`\`sh head -n 20 {prefix}/bases/<base>/<table>/records.jsonl | jq .fields \`\`\``; /** * Airtable bases as directories, tables as records.jsonl files. Records are * live data another client may edit at any moment, so reads are never served * from the file cache; the schema listings still ride the index for its TTL. * The transport is plain fetch, so one class serves node and the browser. */ export class AirtableVFS extends BaseVFS { name = VFSName.AIRTABLE; cachesReads = false; // records.jsonl and the view files render a paged read, so their size is // unknown until the bytes exist. sizesAlwaysKnown = false; supportsSnapshot = false; vfsMd = VFS_MD; accessor; handed = { readdir: ['entry'], read: ['entry', 'parent'], stat: ['entry'], linesContaining: ['entry'], firstLines: ['entry', 'parent'], }; config; constructor(config, options = {}) { super(); this.config = config; this.accessor = new AirtableAccessor(config, options); } readdir(path, handed) { return airtableReaddir(this.accessor, path, handed?.entry); } // eslint-disable-next-line @typescript-eslint/require-await async read(path, handed) { return airtableStream(this.accessor, path, handed?.entry, handed?.parent); } stat(path, handed) { return airtableStat(this.accessor, path, handed?.entry); } linesContaining(path, text, _opts, handed) { return airtableLinesContaining(this.accessor, path, text, handed?.entry); } firstLines(path, n, handed) { return airtableFirstLines(this.accessor, path, n, handed?.entry, handed?.parent); } deltaHook() { return buildDeltaHook(this.accessor); } getState() { return this.configState(AirtableConfig, this.config); } } //# sourceMappingURL=airtable.js.map