mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-22 17:16:19 -07:00
feat(dictionary): add Hachidori backend support
- Add backend selection, setup gating, Anki integration, and external host support - Add launcher flags, documentation, packaging, and focused tests - Open on-demand overlay modals on the first attempt
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
// Canonical persistent lookup rows, shared by the storage owner and backup validator.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import "./lookup-stats-identity.js";
|
||||
export const { normaliseLookupTerm, lookupStatsPrefix, lookupStatsKey } = globalThis.HDLookupStats;
|
||||
export const LOOKUP_STATS_KEY = "lookupStats";
|
||||
export const LOOKUP_STATS_ROW_PREFIX = "lookupStats:";
|
||||
|
||||
export function emptyLookupStats() {
|
||||
return { generation: null, revision: 0 };
|
||||
}
|
||||
|
||||
export function assertLookupStatsDescriptor(descriptor) {
|
||||
if (!descriptor || !Number.isSafeInteger(descriptor.revision) || descriptor.revision < 0
|
||||
|| (descriptor.generation !== null && (typeof descriptor.generation !== "string" || descriptor.generation === ""))) {
|
||||
throw new Error("Invalid lookup statistics descriptor.");
|
||||
}
|
||||
}
|
||||
|
||||
function assertLookupStatsRow(row) {
|
||||
if (!row || typeof row.term !== "string" || row.term.trim() === "" || typeof row.reading !== "string"
|
||||
|| !Number.isSafeInteger(row.lookupCount) || row.lookupCount < 0
|
||||
|| !Number.isFinite(row.firstLookedUpAt) || !Number.isFinite(row.lastLookedUpAt)
|
||||
|| row.firstLookedUpAt > row.lastLookedUpAt) {
|
||||
throw new Error("Invalid lookup statistics row.");
|
||||
}
|
||||
const canonical = normaliseLookupTerm(row.term, row.reading);
|
||||
if (row.term !== canonical.term || row.reading !== canonical.reading) {
|
||||
throw new Error("The lookup statistics row is not canonical.");
|
||||
}
|
||||
}
|
||||
|
||||
export function assertLookupStatsRows(descriptor, rows) {
|
||||
assertLookupStatsDescriptor(descriptor);
|
||||
if (!Array.isArray(rows) || (descriptor.generation === null && rows.length !== 0)) {
|
||||
throw new Error("Invalid lookup statistics collection.");
|
||||
}
|
||||
const keys = new Set();
|
||||
for (const row of rows) {
|
||||
assertLookupStatsRow(row);
|
||||
const key = lookupStatsKey(descriptor, row);
|
||||
if (keys.has(key)) throw new Error("Duplicate lookup statistics rows.");
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
export function incrementLookupStats(previous, term, now) {
|
||||
if (previous !== undefined) {
|
||||
assertLookupStatsRow(previous);
|
||||
if (previous.term !== term.term || previous.reading !== term.reading) {
|
||||
throw new Error("The lookup statistics row does not match its key.");
|
||||
}
|
||||
}
|
||||
const row = { ...term, lookupCount: (previous?.lookupCount ?? 0) + 1,
|
||||
firstLookedUpAt: Math.min(previous?.firstLookedUpAt ?? now, now),
|
||||
lastLookedUpAt: Math.max(previous?.lastLookedUpAt ?? now, now) };
|
||||
// Safe integers are the persisted JSON representation, not a product cap.
|
||||
assertLookupStatsRow(row);
|
||||
return row;
|
||||
}
|
||||
Reference in New Issue
Block a user