UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

134 lines 5.71 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestsPersistenceVolatile = void 0; const TestNotFoundException_1 = require("../../exceptions/TestNotFoundException"); function deepCopy(test) { return JSON.parse(JSON.stringify(test)); } /** * A volatile (in-memory) implementation of TestsPersistence. */ class TestsPersistenceVolatile { constructor(tests = new Map(), /** * Optional flows-source callback used to compute flow-derived sort * keys (`flow_count`, `latest_flow_created_at`). When supplied, the * volatile layer can natively sort by those keys; otherwise they * fall back to the existing best-effort string-index lookup. The * callback is async so the registry can resolve the flows * persistence layer lazily. */ flowsSource) { this.tests = tests; this.flowsSource = flowsSource; } async createTest(testMetadata) { this.tests.set(testMetadata.id, deepCopy(testMetadata)); } async updateTest(testMetadata) { if (!this.tests.has(testMetadata.id)) { throw TestNotFoundException_1.TestNotFoundException.forId(testMetadata.id); } this.tests.set(testMetadata.id, deepCopy(testMetadata)); } async getTestById(testId) { const metadata = this.tests.get(testId); if (!metadata) { throw TestNotFoundException_1.TestNotFoundException.forId(testId); } return deepCopy(metadata); } async getTests(query) { const validLimit = Math.min(Math.max(1, query.limit ?? 100), 100); let filtered = Array.from(this.tests.values()); // partialName takes precedence over name if both are provided. if (query.partialName) { const lower = query.partialName.toLowerCase(); filtered = filtered.filter((t) => t.name?.toLowerCase().includes(lower)); } else if (query.name) { filtered = filtered.filter((t) => t.name === query.name); } if (query.suiteId) { filtered = filtered.filter((t) => t.suiteId === query.suiteId); } const sortCol = query.sortBy ?? 'created_at'; const sortAsc = query.sortOrder === 'asc'; // Pre-compute a per-test summary (count + latest flow) from the flows // source (volatile flows layer in the typical RAM setup; can be any // FlowsPersistence). Done in parallel; results populate `summaries` // before the synchronous sort comparator and the final TestListItem // construction below. Without a flows source we still serve the list // — flowCount defaults to 0 and latestFlow to null. const summaries = new Map(); if (this.flowsSource) { const flowsLayer = await this.flowsSource(); await Promise.all(filtered.map(async (test) => { let count = 0; let latestCreatedAt = 0; let latestFlow = null; let pageToken = undefined; do { const page = await flowsLayer.getFlowsMetadata({ testId: test.id, limit: 100, pageToken, }); count += page.items.length; for (const flow of page.items) { // Volatile flows don't track a separate created_at column; // startedAt is the closest proxy and the same value the // SQLite migration uses for backfilled rows. const ts = flow.startedAt ?? 0; if (ts > latestCreatedAt) { latestCreatedAt = ts; latestFlow = flow; } } pageToken = page.nextPageToken; } while (pageToken); summaries.set(test.id, { count, latestCreatedAt, latestFlow }); })); } filtered.sort((a, b) => { let aVal; let bVal; if (sortCol === 'flow_count') { aVal = summaries.get(a.id)?.count ?? 0; bVal = summaries.get(b.id)?.count ?? 0; } else if (sortCol === 'latest_flow_created_at') { aVal = summaries.get(a.id)?.latestCreatedAt ?? 0; bVal = summaries.get(b.id)?.latestCreatedAt ?? 0; } else { aVal = String(a[sortCol] ?? ''); bVal = String(b[sortCol] ?? ''); } const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0; return sortAsc ? cmp : -cmp; }); const offset = query.pageToken ? parseInt(query.pageToken, 10) : 0; const paged = filtered.slice(offset, offset + validLimit); const hasMore = offset + validLimit < filtered.length; return { items: paged.map((test) => { const summary = summaries.get(test.id); return { ...deepCopy(test), flowCount: summary?.count ?? 0, latestFlow: summary?.latestFlow ?? null, }; }), nextPageToken: hasMore ? (offset + validLimit).toString() : undefined, }; } async deleteTest(testId) { const didExist = this.tests.delete(testId); if (!didExist) { throw TestNotFoundException_1.TestNotFoundException.forId(testId); } } } exports.TestsPersistenceVolatile = TestsPersistenceVolatile; //# sourceMappingURL=TestsPersistenceVolatile.js.map