Files
TREK/server/tests/helpers/storage-release.ts
T
Maurice 1c6a175ebc fix(storage): queue what a delete left unnamed past every undo, and tell a copied database apart
A delete or a replace no longer deletes inside the request. The owner's call
queues the keys in storage_release_queue, due after RELEASE_DELAY_MS (26 h:
the assistant's 24 h approval window plus a margin), and StorageReleaseJob
takes the due rows every ten minutes, at most 200 a tick and 50 keys a
question, asks every reference source again and deletes what none names.
It refuses what the nightly pass refuses (StorageReclaimGuardService: the
ledger, the required sources, a shared location, another installation's
owner marker, cached per category), and waits while a restore or a backup
runs or another storage job holds the slot. An assistant undo or a late
write that names the old value again keeps its object.

The install id lives in data/.trek-install-id, outside any backup and never
written by a restore. At boot and after every reopen the database is
reconciled with it: a database naming another installation (a boot restore,
a restart after a failed restore, a copied travel.db) loses its switch,
confirmations, reports, marks and queue before anything reads them.

A confirmation now stores the count the admin was shown and when; it covers
up to one and a half times that count (at least 100 more) for 14 days, is
used up once the share is back under a fifth, and a count the last report
no longer holds is refused as reclaim_confirm_stale. The admin state folds
a live confirmation in, so the prompt goes at once.

A pass reads each source once for every category and drops each category's
keys once judged; right before a batch it reads the category's sources
again, whole and in memory, instead of a LIKE per key. A narrowed read holds
at most 50 LIKE terms. A pass held off by a backup reports backup_running,
and a category the ledger never reclaims reports not_reclaimed even after a
stop.
2026-11-01 18:11:43 +01:00

43 lines
2.1 KiB
TypeScript

/**
* Stand-ins for StorageReleaseService in suites that hand-build an owner
* service. The release itself (the queue, the job asking every reference
* source, the guards) has its own suites: storage-release.service.test.ts,
* storage-release.job.test.ts and the integration suite over the full app.
*
* - `releaseNothing()` queues nothing and records the calls;
* - `releaseDirect(storage)` deletes every root-level object the values name
* at once, as the queue would once the objects are due in a suite with no
* second row naming them.
*/
import { inReclaimScope } from '../../src/nest/storage/storage-reclaim.ledger';
import { storageKeysIn, type StorageKeyOptions } from '../../src/nest/storage/storage-reclaim.types';
import type { StorageReleaseService } from '../../src/nest/storage/storage-release.service';
import type { StorageService } from '../../src/nest/storage/storage.service';
import type { ServedCategory } from '../../src/nest/storage/storage.types';
import { vi } from 'vitest';
export function releaseNothing() {
const releaseValues = vi.fn(async (): Promise<number> => 0);
return { releaseValues } as unknown as StorageReleaseService & { releaseValues: typeof releaseValues };
}
export function releaseDirect(storage: StorageService): StorageReleaseService {
const releaseValues = async (
values: ReadonlyArray<string | null | undefined>,
options: StorageKeyOptions = {},
): Promise<number> => {
const refs = values.flatMap((value) => storageKeysIn(value, options)).filter((ref) => inReclaimScope(ref.key));
const unique = [...new Map(refs.map((ref) => [`${ref.category}\n${ref.key}`, ref])).values()];
const present = await Promise.all(
unique.map(async (ref) =>
(await storage.stat(ref.category as ServedCategory, ref.key).catch(() => null)) !== null ? ref : null,
),
);
const gone = present.filter((ref): ref is NonNullable<typeof ref> => ref !== null);
await Promise.all(gone.map((ref) => storage.delete(ref.category as ServedCategory, ref.key)));
return gone.length;
};
return { releaseValues } as unknown as StorageReleaseService;
}