@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
138 lines • 6.08 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 { asObject } from "../../utils/records.js";
import { cursorItems, cursorPages } from "../api/paginate.js";
import { completePage } from "./client.js";
import { MAX_PAGE_SIZE } from "./constants.js";
async function paginateTool(transport, toolName, baseArgs, maxResults) {
const items = await cursorItems(async (cursor) => completePage(await transport.callTool(toolName, cursor === null ? { ...baseArgs } : { ...baseArgs, start_cursor: cursor })), maxResults);
return items.map(asObject);
}
export async function searchDataSources(transport) {
const baseArgs = { filter: { value: 'data_source', property: 'object' }, page_size: 100 };
return paginateTool(transport, 'API-post-search', baseArgs);
}
export async function getDatabase(transport, databaseId) {
return transport.callTool('API-retrieve-a-database', { database_id: databaseId });
}
export async function getDataSource(transport, dataSourceId) {
return transport.callTool('API-retrieve-a-data-source', { data_source_id: dataSourceId });
}
/**
* Yield query pages only as the caller requests them. Each page is checked
* for an incomplete response before yielding its rows. Closing the iterator
* prevents further requests; the shared cursor walker retains its
* repeated-cursor and terminal-page rules. Query options are never mutated.
*/
export async function* queryDataSourcePages(transport, dataSourceId, body = {}) {
const baseArgs = { ...body, page_size: MAX_PAGE_SIZE };
for await (const page of cursorPages((cursor) => queryDataSourcePage(transport, dataSourceId, cursor === null ? { ...baseArgs } : { ...baseArgs, start_cursor: cursor }))) {
yield page.map(asObject);
}
}
export async function getPage(transport, pageId) {
return transport.callTool('API-retrieve-a-page', { page_id: pageId });
}
export async function getSelf(transport) {
return transport.callTool('API-get-self', {});
}
export async function getPageMarkdown(transport, pageId) {
return transport.callTool('API-retrieve-page-markdown', { page_id: pageId });
}
export async function replacePageMarkdown(transport, pageId, markdown) {
return transport.callTool('API-patch-page-markdown', {
page_id: pageId,
type: 'replace_content',
replace_content: { new_str: markdown },
});
}
// `ntn datasources query` is explicitly one page at a time: it honors
// --limit, reports has_more and hands the caller the cursor, so it cannot
// use the paginating helper the mount uses. An incomplete page still
// refuses, so a truncated result never prints as a successful query.
export async function queryDataSourcePage(transport, dataSourceId, body) {
return completePage(await transport.callTool('API-post-data-source-query', {
...body,
data_source_id: dataSourceId,
}));
}
export async function listBlockChildren(transport, blockId) {
return paginateTool(transport, 'API-retrieve-block-children', {
block_id: blockId,
page_size: 100,
});
}
const MAX_BLOCK_DEPTH = 10;
export async function listBlockTree(transport, blockId, depth = 0) {
const blocks = await listBlockChildren(transport, blockId);
if (depth >= MAX_BLOCK_DEPTH)
return blocks;
for (const block of blocks) {
const btype = block.type;
if (btype === 'child_page' || btype === 'child_database')
continue;
if (block.has_children === true && typeof block.id === 'string') {
block.children = await listBlockTree(transport, block.id, depth + 1);
}
}
return blocks;
}
export async function getChildPages(transport, parentBlockId) {
const blocks = await listBlockChildren(transport, parentBlockId);
const refs = [];
for (const block of blocks) {
if (block.type !== 'child_page')
continue;
const id = block.id;
if (typeof id !== 'string')
continue;
const childPage = asObject(block.child_page);
const title = childPage.title;
const lastEditedTime = block.last_edited_time;
refs.push({
id,
title: typeof title === 'string' ? title : 'untitled',
lastEditedTime: typeof lastEditedTime === 'string' ? lastEditedTime : '',
});
}
return refs;
}
export async function searchPages(transport, query = '', pageSize = 100, maxResults) {
const baseArgs = {
filter: { value: 'page', property: 'object' },
page_size: Math.min(pageSize, MAX_PAGE_SIZE),
};
if (query !== '')
baseArgs.query = query;
return paginateTool(transport, 'API-post-search', baseArgs, maxResults);
}
export async function createPage(transport, input) {
const parentBody = input.parent.type === 'workspace'
? { type: 'workspace', workspace: true }
: { type: 'page_id', page_id: input.parent.page_id };
const body = {
parent: parentBody,
properties: {
title: { title: [{ type: 'text', text: { content: input.title } }] },
},
};
return transport.callTool('API-post-page', body);
}
export async function createPageRaw(transport, body) {
return transport.callTool('API-post-page', body);
}
export async function updatePage(transport, pageId, body) {
return transport.callTool('API-patch-page', { ...body, page_id: pageId });
}
//# sourceMappingURL=pages.js.map