Convention — helpers catalog
What it is
The small set of shared helper/extension classes extracted during the lookup-CRUD work (MIQ-131/132/133) that a developer building the next feature should reuse rather than re-implement. Each was extracted from observed duplication, not predicted need — see §G discipline — so the catalog is deliberately short.
When to use it
Reach for these when building a lookup-CRUD feature (or any controller that returns ProblemDetails / any service that soft-deletes). Don't extend their locked shapes; if your case doesn't fit, keep your code inline.
Backend helpers
| Helper | File | What it does |
|---|---|---|
SoftDeleteExtensions |
Application/Common/SoftDeleteExtensions.cs |
MarkDeleted(this AuditableEntity, string actor, DateTimeOffset now) — sets IsDeleted=true, DeletedAt, DeletedBy, IsActive=false in one call. Used by every lookup Delete path. |
ControllerProblemExtensions |
API/Common/ControllerProblemExtensions.cs |
RFC 7807 ProblemDetails factory: Problem400(detail), ToValidationProblem(validationResult), and ProblemWithCode(status, title, detail, extensions) for the structured-code 409s. |
ExceptionPredicates |
API/Common/ExceptionPredicates.cs |
IsNotFound() / IsDuplicate() predicates on InvalidOperationException, for catch (… ex) when (ex.IsDuplicate()) mapping to the right status. |
SystemRowProtectedException<T> |
Application/Common/SystemRowProtectedException.cs |
The generic system-row guard (MIQ-133 Phase 1, the PB-085 rule-of-three extraction): (T entity, string code, string localizedMessage), carrying Entity and Code. See Exceptions. |
Frontend helpers
| Helper | File | What it does |
|---|---|---|
createLookupHooks |
web/src/lib/createLookupHooks.ts |
React Query factory: given { resourceKey, api: { list, get, create, update, delete } }, returns { keys, useList, useDetail, useCreate, useUpdate, useDelete }. All seven lookup hook files consume it. |
DeleteConfirmDialog |
web/src/components/common/DeleteConfirmDialog.tsx |
Shared delete-confirmation modal. Takes the delete-mutation hook reference + rowId + testIdPrefix; renders server 409 bodies with whitespace-pre-line so multi-line "referenced by" messages keep their line breaks. |
extractErrorMessage |
web/src/lib/apiError.ts |
Safely pulls the user-facing message out of an Axios error (response.data.detail ?? message ?? null). Used by every dialog. |
How they fit together
A typical lookup delete: the page renders DeleteConfirmDialog, passing its useDelete<Entity>() mutation (from a createLookupHooks factory) and a testIdPrefix. On confirm, the mutation calls the API; a 409 comes back from ProblemWithCode on the server; extractErrorMessage surfaces the localized detail; the dialog shows it with line breaks preserved.
Gotchas / constraints
- The factory and dialog shapes are locked (MIQ-131 decision 39). If a future entity needs a 7th parameter or a different verb, do not extend the helper — keep that entity's hooks/dialog inline, or split the abstraction. Forcing a parameter onto the shared shape is the leaky-abstraction failure the §G discipline exists to prevent.
useSkillsAdminusesresourceKey: 'skills-admin'to partition its React Query cache away from the production skills surface — an example of the coexistence pattern.SystemRowProtectedException<T>reached the catalog at three real consumers, not before — the rule-of-three threshold (MIQ-133, PB-085). Some 4-consumer patterns (theComposeReferencedBodyStringBuilder) were deliberately not extracted because inline still read cleaner.
Build status
Available — all helpers are live and in use across the lookup-CRUD entities (MIQ-131/132/133).
Related
- Exceptions, Lookup CRUD template, §G discipline, Coexistence pattern
- Source: MIQ-131 / MIQ-132 / MIQ-133 reports (
manpoweriq/docs/).