Files
usenet-indexer/lib/release/text_kind.ts
T
Daan MeijerandClaude Fable 5.1 4d0610ca06
Test and deploy / test (push) Successful in 1m4s
Test and deploy / edge (push) Successful in 2m4s
Test and deploy / deploy (push) Skipped
Albums in the Rust edge, and the batch-counter rule (design 3)
A release the window closed on with one file named by its stem or post, the
name with a number and no archive, volume, sidecar or random name, waits in
the album of its poster and template (the name's digit runs as #, the
extension apart) posted within the window of the album's first file; the
album closes a window after that span and is written as one release
(source album, no file total) named by the members' shared numbers, a lone
member as the release it was. The batch store writes an album's member
names as release_texts kind 6 (member). A counter total of 100 or more under
which one poster started 20 or more releases is a batch counter: those
releases get no file total.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-26 12:22:30 +01:00

61 lines
2.4 KiB
TypeScript

/**
* What a searchable string of a release is, as release_texts.kind stores it (a smallint; voyager 3, migration 6200).
* The table holds every string search matches against (the release name too), appended by whoever learns one: the
* batch store the name, the prober the file names inside an archive, later passes NFO titles, par2 set names,
* media titles. The numbers never change meaning once written.
*/
export const TEXT_KINDS = {
/** The release name (releases.name, once more for the one trigram index). */
name: 1,
/** A file name inside an archive (the prober's inner names). */
file: 2,
/** A title read from an NFO. */
nfo: 3,
/** A par2 set's name when it differs from the release name. */
par2: 4,
/** A title from a media container's metadata. */
media: 5,
/** An album's member, by its own file name (the edge's albums: one poster's files differing by a number sequence). */
member: 6,
} as const
export type TextKind = keyof typeof TEXT_KINDS
/** The longest string stored (release_texts.text is a varchar(255)); longer ones are cut, not refused. */
export const TEXT_MAX = 255
/** The most strings one writer appends per release and kind (an archive of two thousand files is not two thousand rows). */
export const TEXTS_PER_KIND = 20
const NAMES = Object.fromEntries(
Object.entries(TEXT_KINDS).map(([name, code]) => [code, name])
) as Record<number, TextKind>
/** The code of a kind; throws for an unknown one (a typo would write a wrong number for good). */
export function textKindCode(name: string): number {
const code = TEXT_KINDS[name as TextKind]
if (code === undefined) throw new Error(`Unknown release text kind "${name}"`)
return code
}
/** The name of a stored code; "unknown" for one not listed. */
export function textKindName(code: number | string | null | undefined): TextKind | 'unknown' {
return NAMES[Number(code)] ?? 'unknown'
}
/**
* The strings a writer appends for one kind: trimmed, cut to TEXT_MAX, empty ones and repeats dropped, the first
* TEXTS_PER_KIND kept in the order given (the writer puts the most telling first).
*/
export function textsToStore(texts: Iterable<string | null | undefined>): string[] {
const seen = new Set<string>()
const out: string[] = []
for (const raw of texts) {
const text = (raw ?? '').trim().slice(0, TEXT_MAX)
if (!text || seen.has(text)) continue
seen.add(text)
out.push(text)
if (out.length === TEXTS_PER_KIND) break
}
return out
}