Skip to content

Fold the repeated domain helpers into one copy each - #514

Open
Pixnop wants to merge 5 commits into
devfrom
refactor/484-domain-helpers
Open

Pixnop wants to merge 5 commits into
devfrom
refactor/484-domain-helpers

Conversation

@Pixnop

@Pixnop Pixnop commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Four shapes that src/domain wrote out once per module now live in one place each. No behaviour changes, no test file is touched, and the hexagonal split is exactly as it was: the ports stay four ports, the modules stay pure, only the restated bodies go.

What changes

isRecord, twelve copies into one (src/domain/records.ts, 56 lines removed). Ten domain modules, src/ipc/optimumOverlay.ts and src/ipc/validation.ts each held a byte-identical private copy of the guard. The audit counted eight; dev has moved since, and optimum/manifest.ts, optimum/ndjson.ts and optimumOverlay.ts had grown their own in the meantime. src/ipc/validation.ts re-exports the shared one, so src/config/configManager.ts, the five IPC handler modules, both e2e scripts and tests/ipc/fetchReleaseNotes.test.ts keep the import path they had. src/domain/redaction.ts set this precedent and its header explains it.

asRecord in account/modPaths.ts goes with them. Its two call sites now narrow with isRecord directly, which is one guard instead of two and the same predicate: value && typeof value === "object" and typeof value === "object" && value !== null accept and reject the same values.

refuse, seven copies into one generic (src/domain/refusal.ts, 32 lines removed). Six modules had the three-line helper and installations/create.ts had it twice, under refuseFields and refuseCreate. They differed only in the failure union, which is now the type parameter. The return type is still written out explicitly, so explicit-function-return-type is satisfied and ok stays the literal false the result unions discriminate on. installations/backup.ts and installations/restore.ts keep their own helpers.

The four host outcomes get one shape (src/domain/ports.ts, 2 interfaces removed). CompressOutcome, ExtractOutcome and UnpackOutcome were byte-identical; they now alias HostOutcome, and DownloadOutcome extends it with its filePath. The four port interfaces, and every name they answer under, are untouched.

GameBuildVariant is declared once (src/global.d.ts, one declaration and one duplicated doc comment removed). The ambient GameBuildVariantType now imports the domain type instead of restating the "Optimum" literal and the rule behind it. Raised in the review of #457.

What stays

Nothing was skipped: all four findings applied as written, plus the three isRecord copies the audit predates.

The deliberate rules are untouched. The path policy, the IPC validation at the boundary, the mutation-tested guards and the accessibility wiring are not in this diff, and none of tests/security-boundaries.test.ts, tests/log-provenance.test.ts, tests/text-contrast.test.ts or tests/i18n/i18n-parity.test.ts is relaxed by anything here. toWireBuildVariant still holds the wire value to the fixed token; the alias only moves where that token is spelled.

Behaviour

No test file is added, changed or deleted. Every caller was checked by grep: the isRecord call sites all reach it through the two names they already used, the domain module inside src/domain and the src/ipc/validation.ts re-export everywhere else; refuse has 22 call sites, all inside the six modules that used to declare it; the outcome types are type-only and TypeScript is structural; the variant type has five referencing files and all three tsconfigs resolve the alias.

Gate, against origin/dev at 5ddf93a:

dev here
Test files 242 242
Tests 4463 (2 skipped) 4461 passed, 2 skipped
Statements 94.58% (8866/9374) 94.57% (8849/9357)
Branches 91.02% (4952/5440) 90.96% (4913/5401)
Functions 95.22% (1876/1970) 95.18% (1858/1952)
Lines 96.26% (7471/7761) 96.25% (7453/7743)

The percentages move by hundredths because the deleted lines were all covered: 17 statements, 39 branches and 18 functions leave the denominator along with the same count in the numerator. No uncovered code is added.

Worth knowing for whoever reruns this locally: three runs of the suite on dev itself, on this machine, each failed a different two or three timing-sensitive tests (activity centre focus, mod profiles, the compression and ModDB listing handlers), which is load and not the tree. The branch ran green twice, the coverage run included.

npm run typecheck clean across the three configs, npm run lint:ci 0 errors and the same 14 pre-existing warnings, npm run format:check clean. git merge-tree against the six open PR branches reports no conflict.

Closes #484. Part of #492.

Eleven modules and src/ipc/validation.ts each carried a byte-identical
private copy of the object guard. They now share src/domain/records.ts,
which validation.ts re-exports so every host caller and test keeps its
import path. modPaths.ts drops its asRecord variant for the same guard.
Six modules restated the same three-line helper once per failure union,
create.ts twice. src/domain/refusal.ts carries the union as a type
parameter instead and returns the same { ok: false, reason } shape. The
richer refusals in backup.ts and restore.ts keep their own helpers.
CompressOutcome, ExtractOutcome and UnpackOutcome were byte-identical
interfaces; DownloadOutcome was the same plus filePath. They now alias or
extend HostOutcome, so the shape is written once. The four ports and
every name they answer under are unchanged.
GameBuildVariant was spelled out twice, once in src/domain/versions/detect.ts
and once as the ambient GameBuildVariantType, doc comment included. The
ambient name now imports the domain type, so the Optimum token lives in one
place and cannot drift. Resolves under all three tsconfigs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant