@aplaytest/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/config/schema.d.ts +247 -0
  3. package/dist/config/schema.d.ts.map +1 -0
  4. package/dist/config/schema.js +279 -0
  5. package/dist/config/schema.js.map +1 -0
  6. package/dist/evidence/id.d.ts +17 -0
  7. package/dist/evidence/id.d.ts.map +1 -0
  8. package/dist/evidence/id.js +20 -0
  9. package/dist/evidence/id.js.map +1 -0
  10. package/dist/evidence/redact.d.ts +22 -0
  11. package/dist/evidence/redact.d.ts.map +1 -0
  12. package/dist/evidence/redact.js +97 -0
  13. package/dist/evidence/redact.js.map +1 -0
  14. package/dist/evidence/store.d.ts +51 -0
  15. package/dist/evidence/store.d.ts.map +1 -0
  16. package/dist/evidence/store.js +148 -0
  17. package/dist/evidence/store.js.map +1 -0
  18. package/dist/evidence/types.d.ts +183 -0
  19. package/dist/evidence/types.d.ts.map +1 -0
  20. package/dist/evidence/types.js +21 -0
  21. package/dist/evidence/types.js.map +1 -0
  22. package/dist/history/ingest-playwright.d.ts +26 -0
  23. package/dist/history/ingest-playwright.d.ts.map +1 -0
  24. package/dist/history/ingest-playwright.js +73 -0
  25. package/dist/history/ingest-playwright.js.map +1 -0
  26. package/dist/history/ingest.d.ts +29 -0
  27. package/dist/history/ingest.d.ts.map +1 -0
  28. package/dist/history/ingest.js +86 -0
  29. package/dist/history/ingest.js.map +1 -0
  30. package/dist/history/memory-index.d.ts +88 -0
  31. package/dist/history/memory-index.d.ts.map +1 -0
  32. package/dist/history/memory-index.js +236 -0
  33. package/dist/history/memory-index.js.map +1 -0
  34. package/dist/history/playwright-report.d.ts +102 -0
  35. package/dist/history/playwright-report.d.ts.map +1 -0
  36. package/dist/history/playwright-report.js +274 -0
  37. package/dist/history/playwright-report.js.map +1 -0
  38. package/dist/history/store.d.ts +73 -0
  39. package/dist/history/store.d.ts.map +1 -0
  40. package/dist/history/store.js +243 -0
  41. package/dist/history/store.js.map +1 -0
  42. package/dist/history/types.d.ts +69 -0
  43. package/dist/history/types.d.ts.map +1 -0
  44. package/dist/history/types.js +18 -0
  45. package/dist/history/types.js.map +1 -0
  46. package/dist/history/url.d.ts +79 -0
  47. package/dist/history/url.d.ts.map +1 -0
  48. package/dist/history/url.js +121 -0
  49. package/dist/history/url.js.map +1 -0
  50. package/dist/index.d.ts +39 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +31 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/locator/stability.d.ts +63 -0
  55. package/dist/locator/stability.d.ts.map +1 -0
  56. package/dist/locator/stability.js +127 -0
  57. package/dist/locator/stability.js.map +1 -0
  58. package/dist/taxonomy/classify.d.ts +63 -0
  59. package/dist/taxonomy/classify.d.ts.map +1 -0
  60. package/dist/taxonomy/classify.js +344 -0
  61. package/dist/taxonomy/classify.js.map +1 -0
  62. package/dist/taxonomy/kinds.d.ts +49 -0
  63. package/dist/taxonomy/kinds.d.ts.map +1 -0
  64. package/dist/taxonomy/kinds.js +139 -0
  65. package/dist/taxonomy/kinds.js.map +1 -0
  66. package/dist/taxonomy/parse-error.d.ts +85 -0
  67. package/dist/taxonomy/parse-error.d.ts.map +1 -0
  68. package/dist/taxonomy/parse-error.js +179 -0
  69. package/dist/taxonomy/parse-error.js.map +1 -0
  70. package/dist/version.d.ts +10 -0
  71. package/dist/version.d.ts.map +1 -0
  72. package/dist/version.js +10 -0
  73. package/dist/version.js.map +1 -0
  74. package/dist-cjs/.tsbuildinfo +1 -0
  75. package/dist-cjs/config/schema.js +283 -0
  76. package/dist-cjs/config/schema.js.map +1 -0
  77. package/dist-cjs/evidence/id.js +24 -0
  78. package/dist-cjs/evidence/id.js.map +1 -0
  79. package/dist-cjs/evidence/redact.js +103 -0
  80. package/dist-cjs/evidence/redact.js.map +1 -0
  81. package/dist-cjs/evidence/store.js +156 -0
  82. package/dist-cjs/evidence/store.js.map +1 -0
  83. package/dist-cjs/evidence/types.js +25 -0
  84. package/dist-cjs/evidence/types.js.map +1 -0
  85. package/dist-cjs/history/ingest-playwright.js +76 -0
  86. package/dist-cjs/history/ingest-playwright.js.map +1 -0
  87. package/dist-cjs/history/ingest.js +89 -0
  88. package/dist-cjs/history/ingest.js.map +1 -0
  89. package/dist-cjs/history/memory-index.js +242 -0
  90. package/dist-cjs/history/memory-index.js.map +1 -0
  91. package/dist-cjs/history/playwright-report.js +277 -0
  92. package/dist-cjs/history/playwright-report.js.map +1 -0
  93. package/dist-cjs/history/store.js +247 -0
  94. package/dist-cjs/history/store.js.map +1 -0
  95. package/dist-cjs/history/types.js +23 -0
  96. package/dist-cjs/history/types.js.map +1 -0
  97. package/dist-cjs/history/url.js +127 -0
  98. package/dist-cjs/history/url.js.map +1 -0
  99. package/dist-cjs/index.js +88 -0
  100. package/dist-cjs/index.js.map +1 -0
  101. package/dist-cjs/locator/stability.js +134 -0
  102. package/dist-cjs/locator/stability.js.map +1 -0
  103. package/dist-cjs/package.json +3 -0
  104. package/dist-cjs/taxonomy/classify.js +348 -0
  105. package/dist-cjs/taxonomy/classify.js.map +1 -0
  106. package/dist-cjs/taxonomy/kinds.js +145 -0
  107. package/dist-cjs/taxonomy/kinds.js.map +1 -0
  108. package/dist-cjs/taxonomy/parse-error.js +186 -0
  109. package/dist-cjs/taxonomy/parse-error.js.map +1 -0
  110. package/dist-cjs/version.js +13 -0
  111. package/dist-cjs/version.js.map +1 -0
  112. package/package.json +39 -0
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ /**
3
+ * Ingest a Playwright JSON report into the history store.
4
+ *
5
+ * The I/O half of the adapter: read, parse, hand to `mapPlaywrightReport`,
6
+ * store. All of the mapping decisions — identity, timestamps, classification —
7
+ * live in `playwright-report.ts` and are unit-tested without a database.
8
+ *
9
+ * Like `ingestDirectory`, this NEVER throws. A bad report is reported through
10
+ * `IngestResult.skipped` so that one unreadable artifact among many cannot cost
11
+ * the others, and so a CI analyze job degrades to "nothing ingested" rather
12
+ * than to a red build.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ingestPlaywrightJson = ingestPlaywrightJson;
16
+ const promises_1 = require("node:fs/promises");
17
+ const playwright_report_js_1 = require("./playwright-report.js");
18
+ function isRecord(value) {
19
+ return typeof value === 'object' && value !== null;
20
+ }
21
+ /**
22
+ * The same environment variables the reporter reads. Kept identical on purpose:
23
+ * a run ingested through the JSON path and one written by the reporter should
24
+ * be labelled by the same rules, or comparing them later means comparing the
25
+ * conventions of two different code paths.
26
+ */
27
+ function identityFromEnv(overrides) {
28
+ const env = process.env;
29
+ return {
30
+ commit: overrides?.commit ?? env['GITHUB_SHA'] ?? null,
31
+ branch: overrides?.branch ?? env['GITHUB_REF_NAME'] ?? null,
32
+ appEnv: overrides?.appEnv ?? env['APP_ENV'] ?? null,
33
+ ci: overrides?.ci ?? (env['CI'] === 'true' || env['CI'] === '1'),
34
+ };
35
+ }
36
+ function failed(path, reason) {
37
+ return { filesRead: 1, runsIngested: 0, attemptsIngested: 0, skipped: [{ file: path, reason }] };
38
+ }
39
+ async function ingestPlaywrightJson(store, path, options = {}) {
40
+ const raw = await (0, promises_1.readFile)(path, 'utf8').catch(() => null);
41
+ if (raw === null) {
42
+ return { filesRead: 0, runsIngested: 0, attemptsIngested: 0, skipped: [{ file: path, reason: 'unreadable' }] };
43
+ }
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(raw);
47
+ }
48
+ catch {
49
+ return failed(path, 'not valid JSON');
50
+ }
51
+ if (!isRecord(parsed))
52
+ return failed(path, 'not a Playwright JSON report');
53
+ const { run, collapsed } = (0, playwright_report_js_1.mapPlaywrightReport)(parsed, {
54
+ identity: identityFromEnv(options.identity),
55
+ ...(options.now === undefined ? {} : { now: options.now }),
56
+ });
57
+ try {
58
+ await store.ingest(run);
59
+ }
60
+ catch (error) {
61
+ return failed(path, `could not be stored: ${error instanceof Error ? error.message : String(error)}`);
62
+ }
63
+ return {
64
+ filesRead: 1,
65
+ runsIngested: 1,
66
+ attemptsIngested: run.attempts.length,
67
+ // Surfaced through the channel the CLI already prints as warnings. A
68
+ // collapsed duplicate is a real loss of an attempt and has to be visible;
69
+ // it is not a reason to reject the other hundred-odd rows.
70
+ skipped: collapsed.map(what => ({
71
+ file: path,
72
+ reason: `duplicate attempt collapsed: ${what}`,
73
+ })),
74
+ };
75
+ }
76
+ //# sourceMappingURL=ingest-playwright.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest-playwright.js","sourceRoot":"","sources":["../../src/history/ingest-playwright.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ /**
3
+ * Ingest run records written by the reporter into the history store.
4
+ *
5
+ * The reporter writes plain JSON; this is the seam where those files become
6
+ * queryable history. Keeping it separate means a CI job can download shard
7
+ * artifacts from anywhere and ingest them in one pass, and a developer can
8
+ * replay months of archived runs to seed history without waiting for it to
9
+ * accumulate.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ingestDirectory = ingestDirectory;
13
+ const promises_1 = require("node:fs/promises");
14
+ const node_path_1 = require("node:path");
15
+ const types_js_1 = require("./types.js");
16
+ function isRecord(value) {
17
+ return typeof value === 'object' && value !== null;
18
+ }
19
+ function isRunRecord(value) {
20
+ if (!isRecord(value))
21
+ return false;
22
+ if (value['schemaVersion'] !== types_js_1.RUN_SCHEMA_VERSION)
23
+ return false;
24
+ if (typeof value['runId'] !== 'string' || value['runId'] === '')
25
+ return false;
26
+ return Array.isArray(value['attempts']);
27
+ }
28
+ function validate(parsed) {
29
+ if (!isRecord(parsed))
30
+ return 'not an object';
31
+ if (parsed['schemaVersion'] !== types_js_1.RUN_SCHEMA_VERSION) {
32
+ return `schemaVersion ${String(parsed['schemaVersion'])} — this build reads ${types_js_1.RUN_SCHEMA_VERSION}`;
33
+ }
34
+ if (typeof parsed['runId'] !== 'string' || parsed['runId'] === '')
35
+ return 'missing runId';
36
+ if (!Array.isArray(parsed['attempts']))
37
+ return 'missing attempts';
38
+ return isRunRecord(parsed) ? parsed : 'not an object';
39
+ }
40
+ /**
41
+ * Ingest every `*.json` in a directory.
42
+ *
43
+ * A malformed or version-mismatched file is SKIPPED and reported, never
44
+ * thrown: one bad artifact among fifty shards must not cost you the other
45
+ * forty-nine, and a partial history is far more useful than none.
46
+ */
47
+ async function ingestDirectory(store, dir) {
48
+ const entries = await (0, promises_1.readdir)(dir).catch(() => []);
49
+ const files = entries.filter(name => name.endsWith('.json'));
50
+ const skipped = [];
51
+ let runsIngested = 0;
52
+ let attemptsIngested = 0;
53
+ for (const name of files) {
54
+ const path = (0, node_path_1.join)(dir, name);
55
+ let parsed;
56
+ try {
57
+ parsed = JSON.parse(await (0, promises_1.readFile)(path, 'utf8'));
58
+ }
59
+ catch (error) {
60
+ skipped.push({ file: name, reason: error instanceof Error ? error.message : 'unreadable' });
61
+ continue;
62
+ }
63
+ const validated = validate(parsed);
64
+ if (typeof validated === 'string') {
65
+ skipped.push({ file: name, reason: validated });
66
+ continue;
67
+ }
68
+ // The doc comment above promises a bad file is skipped, never thrown —
69
+ // but only the JSON parse and the shape check were guarded. A record that
70
+ // passes both and then fails at the database (an unbindable value, a
71
+ // constraint) threw straight out of here and rolled back the transaction,
72
+ // costing every other file in the batch. In CI that reads as history
73
+ // simply never accumulating.
74
+ try {
75
+ await store.ingest(validated);
76
+ }
77
+ catch (error) {
78
+ skipped.push({
79
+ file: name,
80
+ reason: `could not be stored: ${error instanceof Error ? error.message : String(error)}`,
81
+ });
82
+ continue;
83
+ }
84
+ runsIngested += 1;
85
+ attemptsIngested += validated.attempts.length;
86
+ }
87
+ return { filesRead: files.length, runsIngested, attemptsIngested, skipped };
88
+ }
89
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/history/ingest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ /**
3
+ * The query engine, with no storage under it.
4
+ *
5
+ * Every driver ends up answering the same four questions — attempts in a
6
+ * window, distinct test keys, run count, prune — and the SQL that answers them
7
+ * carries semantics that took real incidents to get right: shard-scoped
8
+ * replacement, earliest-shard `startedAt`, ordering before the limit. Writing
9
+ * those a second time in a blob driver would mean maintaining two definitions
10
+ * of "the same history" and discovering the drift months later, as a flake
11
+ * score that differs between local and CI.
12
+ *
13
+ * So the semantics live here, once, over plain objects. `SqliteHistoryStore`
14
+ * keeps its own SQL (it is the local/dev driver and the schema is its own
15
+ * documentation); the Azure driver is this index plus blob I/O, which is the
16
+ * only shape that makes sense when the durable form is an append-only log of
17
+ * run records rather than a queryable database.
18
+ *
19
+ * Pure and synchronous: no clock, no filesystem, no network. That is what
20
+ * makes the query semantics testable without a container or an emulator.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.MemoryHistoryStore = exports.HistoryIndex = void 0;
24
+ exports.shardKeyOf = shardKeyOf;
25
+ /**
26
+ * Identifies the unit that is replaced wholesale on re-ingest.
27
+ *
28
+ * NOT the run id. Under sharding every shard reports the same run id, so
29
+ * keying on it alone means ingesting shard 2 discards shard 1 — the bug that
30
+ * once turned three shard files carrying four attempts into zero. A shard is
31
+ * the smallest thing a re-run can replace, so it is the key.
32
+ */
33
+ function shardKeyOf(shard) {
34
+ return shard === null || shard === undefined ? 'all' : `${shard.current}-of-${shard.total}`;
35
+ }
36
+ /**
37
+ * Composite-key separator. NUL rather than a space or a colon, because a run id
38
+ * is whatever `ATEST_RUN_ID` contained and both of those are legal in one — and
39
+ * `"a b" + sep + "c"` colliding with `"a" + sep + "b c"` would silently merge
40
+ * two runs into one.
41
+ */
42
+ const KEY_SEP = '\u0000';
43
+ function asStringArray(value) {
44
+ return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
45
+ }
46
+ /**
47
+ * Records reaching here have been shape-checked, not type-checked: `validate()`
48
+ * in ingest.ts asserts a schema version, a run id and an attempts array, and
49
+ * nothing else. A record written by a different atest version can be missing
50
+ * any optional field entirely, and `undefined` where the type says `T | null`
51
+ * propagates into scores as a silent NaN rather than an error.
52
+ */
53
+ function normalizeAttempt(attempt, segment, runMeta) {
54
+ return {
55
+ runId: segment.runId,
56
+ testId: attempt.testId,
57
+ project: attempt.project,
58
+ retry: attempt.retry ?? 0,
59
+ title: attempt.title,
60
+ titlePath: asStringArray(attempt.titlePath),
61
+ file: attempt.file,
62
+ line: attempt.line ?? 0,
63
+ tags: asStringArray(attempt.tags),
64
+ outcome: attempt.outcome,
65
+ failureKind: (attempt.failureKind ?? null),
66
+ durationMs: attempt.durationMs ?? 0,
67
+ workerIndex: attempt.workerIndex ?? 0,
68
+ // An attempt executed during shard 1 of 3 belongs to shard 1 of 3.
69
+ // Falling back to the run's shard is what keeps the replacement key and
70
+ // the attempt's own record of itself in agreement.
71
+ shard: attempt.shard ?? segment.run.shard ?? null,
72
+ traceId: attempt.traceId ?? null,
73
+ evidenceId: (attempt.evidenceId ?? null),
74
+ coScheduled: asStringArray(attempt.coScheduled),
75
+ routes: asStringArray(attempt.routes),
76
+ startedAt: runMeta.startedAt,
77
+ commit: runMeta.commit,
78
+ branch: runMeta.branch,
79
+ ci: runMeta.ci,
80
+ workers: runMeta.workers,
81
+ };
82
+ }
83
+ /**
84
+ * In-memory history with SQLite's query semantics and none of its storage.
85
+ *
86
+ * Add segments in any order; the answers do not depend on it, except where
87
+ * SQLite's own answers would not either (last writer wins on mutable run
88
+ * metadata).
89
+ */
90
+ class HistoryIndex {
91
+ /** Keyed `runId\x00shardKey` — the replacement unit. */
92
+ segments = new Map();
93
+ add(run) {
94
+ const shardKey = shardKeyOf(run.shard);
95
+ this.segments.set(`${run.runId}${KEY_SEP}${shardKey}`, { runId: run.runId, shardKey, run });
96
+ }
97
+ /** Drop everything, so a driver can reload from its backing store. */
98
+ clear() {
99
+ this.segments.clear();
100
+ }
101
+ has(runId, shardKey) {
102
+ return this.segments.has(`${runId}${KEY_SEP}${shardKey}`);
103
+ }
104
+ /**
105
+ * One metadata row per run id, merged across its shards.
106
+ *
107
+ * `startedAt` takes the EARLIEST value: a run spanning six shards started
108
+ * when its first shard did, and letting the last shard ingested stamp the
109
+ * time would move the run inside the recency window depending on artifact
110
+ * download order. Everything else is last-writer-wins, matching the UPSERT.
111
+ */
112
+ runMetadata() {
113
+ const meta = new Map();
114
+ for (const segment of this.segments.values()) {
115
+ const run = segment.run;
116
+ const existing = meta.get(run.runId);
117
+ if (existing === undefined) {
118
+ meta.set(run.runId, {
119
+ startedAt: run.startedAt,
120
+ commit: run.commit ?? null,
121
+ branch: run.branch ?? null,
122
+ ci: run.ci === true,
123
+ workers: run.workers ?? 0,
124
+ });
125
+ continue;
126
+ }
127
+ if (run.startedAt < existing.startedAt)
128
+ existing.startedAt = run.startedAt;
129
+ existing.commit = run.commit ?? null;
130
+ existing.branch = run.branch ?? null;
131
+ existing.ci = run.ci === true;
132
+ existing.workers = run.workers ?? 0;
133
+ }
134
+ return meta;
135
+ }
136
+ attempts(query = {}) {
137
+ const meta = this.runMetadata();
138
+ const rows = [];
139
+ for (const segment of this.segments.values()) {
140
+ const runMeta = meta.get(segment.runId);
141
+ if (runMeta === undefined)
142
+ continue;
143
+ if (query.since !== undefined && runMeta.startedAt < query.since)
144
+ continue;
145
+ for (const attempt of segment.run.attempts ?? []) {
146
+ if (query.testId !== undefined && attempt.testId !== query.testId)
147
+ continue;
148
+ if (query.project !== undefined && attempt.project !== query.project)
149
+ continue;
150
+ rows.push(normalizeAttempt(attempt, segment, runMeta));
151
+ }
152
+ }
153
+ // `ORDER BY r.started_at DESC, a.retry ASC`, then LIMIT — the limit is a
154
+ // window over the newest attempts, so applying it before the sort would
155
+ // silently score an arbitrary subset. The extra tiebreakers are not in the
156
+ // SQL: SQLite leaves ties unordered, and an unstable order here would make
157
+ // a `--limit`ed score depend on Map iteration order.
158
+ rows.sort((a, b) => b.startedAt.localeCompare(a.startedAt) ||
159
+ a.retry - b.retry ||
160
+ a.runId.localeCompare(b.runId) ||
161
+ a.testId.localeCompare(b.testId) ||
162
+ a.project.localeCompare(b.project));
163
+ return query.limit === undefined ? rows : rows.slice(0, Math.max(0, query.limit));
164
+ }
165
+ testKeys() {
166
+ // Newest first, so the title and file a key reports are the ones the test
167
+ // has NOW. SQLite's bare-column GROUP BY picks an arbitrary row, which
168
+ // after a rename reports whichever the query planner happened to visit.
169
+ const keys = new Map();
170
+ for (const attempt of this.attempts()) {
171
+ const key = `${attempt.testId}${KEY_SEP}${attempt.project}`;
172
+ if (keys.has(key))
173
+ continue;
174
+ keys.set(key, {
175
+ testId: attempt.testId,
176
+ project: attempt.project,
177
+ title: attempt.title,
178
+ file: attempt.file,
179
+ });
180
+ }
181
+ return [...keys.values()].sort((a, b) => a.file.localeCompare(b.file) || a.title.localeCompare(b.title));
182
+ }
183
+ runCount() {
184
+ const ids = new Set();
185
+ for (const segment of this.segments.values())
186
+ ids.add(segment.runId);
187
+ return ids.size;
188
+ }
189
+ /**
190
+ * Remove runs that started before the cutoff. Returns the number of RUNS
191
+ * dropped, not segments — a six-shard run pruned is one run, which is what
192
+ * `runCount()` counted it as.
193
+ */
194
+ prune(olderThanIso) {
195
+ const meta = this.runMetadata();
196
+ const doomed = new Set();
197
+ for (const [runId, runMeta] of meta) {
198
+ if (runMeta.startedAt < olderThanIso)
199
+ doomed.add(runId);
200
+ }
201
+ const removedKeys = [];
202
+ for (const [key, segment] of this.segments) {
203
+ if (!doomed.has(segment.runId))
204
+ continue;
205
+ removedKeys.push(key);
206
+ this.segments.delete(key);
207
+ }
208
+ return { removedRuns: doomed.size, removedKeys };
209
+ }
210
+ }
211
+ exports.HistoryIndex = HistoryIndex;
212
+ /**
213
+ * `HistoryStore` over a `HistoryIndex` and nothing else.
214
+ *
215
+ * This is what `--db :memory:` should always have meant. Reaching for
216
+ * `new SqliteHistoryStore(':memory:')` to get a throwaway store — as the MCP
217
+ * server and `aplaytest impact` both did — pulls in `node:sqlite`, a schema and a
218
+ * transaction to hold data that is discarded when the process exits.
219
+ */
220
+ class MemoryHistoryStore {
221
+ index = new HistoryIndex();
222
+ async ingest(run) {
223
+ this.index.add(run);
224
+ }
225
+ async attempts(query = {}) {
226
+ return this.index.attempts(query);
227
+ }
228
+ async testKeys() {
229
+ return this.index.testKeys();
230
+ }
231
+ async runCount() {
232
+ return this.index.runCount();
233
+ }
234
+ async prune(olderThanIso) {
235
+ return this.index.prune(olderThanIso).removedRuns;
236
+ }
237
+ async close() {
238
+ this.index.clear();
239
+ }
240
+ }
241
+ exports.MemoryHistoryStore = MemoryHistoryStore;
242
+ //# sourceMappingURL=memory-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-index.js","sourceRoot":"","sources":["../../src/history/memory-index.ts"],"names":[],"mappings":"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"}