donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
134 lines • 5.71 kB
JavaScript
;
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