Both workspaces set strict: true, which is most of the value. The compiler flag that would catch the most remaining real defects here is not in strict, and has to be asked for.
The pattern it catches, which this codebase uses everywhere
rows[0].id — indexing an array and immediately reading a property. TypeScript types rows[0] as the element type regardless of whether the array has any elements, so the compiler is satisfied by code that throws at runtime. noUncheckedIndexedAccess types it as T | undefined instead and demands the check.
The backend does this after a rows.length guard in most places, which is correct and would satisfy the flag with a small change. In a handful it does not guard at all, and those are the ones worth finding — a query that returns nothing where the code assumed a row produces Cannot read properties of undefined, surfacing as a 500 with no useful message.
Right now pool.query() returns any rows, so rows[0].id is already unchecked and the flag has nothing to bite on — it would report little and prove less. #65 stage 3 gives query results real row types. After that, noUncheckedIndexedAccess is what makes those types honest about emptiness.
Enabling it first would be measuring the wrong thing and would produce a violation count that changes completely once #65 lands. Doing it in the other order means each change is verifiable on its own.
What else was checked and is already fine
Worth recording so nobody re-investigates:
No enum anywhere. The TypeScript team steers away from them for exactly the reasons this codebase already avoids — union types of string literals are used throughout instead, as in ItemStatus.
Two non-null assertions in the whole codebase, both defensible: document.getElementById('root')! is the standard React entry idiom, and byEmail.get(row.email)! in server.ts follows a set on the same key.
strict: true on both sides, and no // @ts-ignore or @ts-expect-error anywhere.
The TypeScript hygiene here is good. This one flag is the gap.
Worth deciding
Whether to also take exactOptionalPropertyTypes and noImplicitOverride in the same pass. Both are smaller, and exactOptionalPropertyTypes in particular interacts with how optional props are spread into antd components, so it deserves its own measurement rather than being assumed cheap.
Verification
The violation count, measured before and after, and the full suite green. Any site the flag exposes as a genuinely unguarded index is a bug fix rather than an annotation, and should be called out as one rather than silenced with a !.
Severity
Low today, and only becomes worth doing after #65. Filed now so the sequencing is recorded rather than rediscovered.
Found during a TypeScript best-practices review.
Both workspaces set `strict: true`, which is most of the value. The compiler flag that would catch the most remaining real defects here is not in `strict`, and has to be asked for.
## The pattern it catches, which this codebase uses everywhere
`rows[0].id` — indexing an array and immediately reading a property. TypeScript types `rows[0]` as the element type regardless of whether the array has any elements, so the compiler is satisfied by code that throws at runtime. `noUncheckedIndexedAccess` types it as `T | undefined` instead and demands the check.
The backend does this after a `rows.length` guard in most places, which is correct and would satisfy the flag with a small change. In a handful it does not guard at all, and those are the ones worth finding — a query that returns nothing where the code assumed a row produces `Cannot read properties of undefined`, surfacing as a 500 with no useful message.
## Why this is sequenced after #65, not before
Right now `pool.query()` returns `any` rows, so `rows[0].id` is already unchecked and the flag has nothing to bite on — it would report little and prove less. #65 stage 3 gives query results real row types. **After** that, `noUncheckedIndexedAccess` is what makes those types honest about emptiness.
Enabling it first would be measuring the wrong thing and would produce a violation count that changes completely once #65 lands. Doing it in the other order means each change is verifiable on its own.
## What else was checked and is already fine
Worth recording so nobody re-investigates:
- **No `enum` anywhere.** The TypeScript team steers away from them for exactly the reasons this codebase already avoids — union types of string literals are used throughout instead, as in `ItemStatus`.
- **Two non-null assertions in the whole codebase**, both defensible: `document.getElementById('root')!` is the standard React entry idiom, and `byEmail.get(row.email)!` in `server.ts` follows a `set` on the same key.
- **`strict: true` on both sides**, and no `// @ts-ignore` or `@ts-expect-error` anywhere.
The TypeScript hygiene here is good. This one flag is the gap.
## Worth deciding
Whether to also take `exactOptionalPropertyTypes` and `noImplicitOverride` in the same pass. Both are smaller, and `exactOptionalPropertyTypes` in particular interacts with how optional props are spread into antd components, so it deserves its own measurement rather than being assumed cheap.
## Verification
The violation count, measured before and after, and the full suite green. Any site the flag exposes as a genuinely unguarded index is a bug fix rather than an annotation, and should be called out as one rather than silenced with a `!`.
## Severity
Low today, and only becomes worth doing after #65. Filed now so the sequencing is recorded rather than rediscovered.
Found during a TypeScript best-practices review.
bermudalamb
added this to the Code Quality and Hardening 2 project 2026-08-21 16:08:39 -05:00
bermudalamb
self-assigned this 2026-08-21 16:09:07 -05:00
Still relevant, but the dependency this issue is waiting on has gone stale and needs re-pointing before the work can start.
This issue holds itself back until "#65 stage 3 gives query results real row types". #65 is closed — but #65 turned out to be Import convention and avoidable any, which has no stages and never included a typing programme for query results. What it did deliver in this area was narrow: publicCustomer(c: any) became a CustomerRow, and the PoolClient annotations in cartCheckout.ts.
Measured on main just now, the precondition is not met:
pool.query<T>(...) with a row type: 1
pool.query(...) total: 89
rows[0] reads: 36
So 88 of 89 queries still return any rows, and this issue's own reasoning still applies unchanged — noUncheckedIndexedAccess would have almost nothing to bite on, would report a count that means little, and that count would change completely once the rows are actually typed.
The blocker is real; it is just no longer represented by an open issue. Typing the query results needs an issue of its own, and this one should depend on that instead of on #65. Filing it is the next step here — this issue should stay open and stay parked until then rather than being started.
Nothing else about the analysis has drifted. The enum and non-null-assertion findings recorded above still hold: no enum anywhere, and the same two defensible ! uses.
Still relevant, but the dependency this issue is waiting on has gone stale and needs re-pointing before the work can start.
This issue holds itself back until "#65 stage 3 gives query results real row types". #65 is closed — but #65 turned out to be *Import convention and avoidable `any`*, which has no stages and never included a typing programme for query results. What it did deliver in this area was narrow: `publicCustomer(c: any)` became a `CustomerRow`, and the `PoolClient` annotations in `cartCheckout.ts`.
Measured on `main` just now, the precondition is not met:
- `pool.query<T>(...)` with a row type: **1**
- `pool.query(...)` total: **89**
- `rows[0]` reads: **36**
So 88 of 89 queries still return `any` rows, and this issue's own reasoning still applies unchanged — `noUncheckedIndexedAccess` would have almost nothing to bite on, would report a count that means little, and that count would change completely once the rows are actually typed.
The blocker is real; it is just no longer represented by an open issue. Typing the query results needs an issue of its own, and this one should depend on that instead of on #65. Filing it is the next step here — this issue should stay open and stay parked until then rather than being started.
Nothing else about the analysis has drifted. The `enum` and non-null-assertion findings recorded above still hold: no `enum` anywhere, and the same two defensible `!` uses.
The missing dependency now exists: #159, Query results are untyped, so strict: true stops at the database boundary.
This issue depends on that one, not on #65. #65 is closed and was Import convention and avoidable any — it had no stages, and the two things it delivered in this area were CustomerRow and the PoolClient annotations in cartCheckout.ts. The "stage 3" this issue waits for was never part of it.
The sequencing argument in the body still holds exactly as written, and #159 records the measurement that backs it: 1 of roughly 184 query sites carries a row type. Until that changes, rows[0] is any, any indexes to any, and noUncheckedIndexedAccess would report a count that means little and that changes completely once the rows are typed.
One correction to this issue's own framing, repeated from an earlier comment because it affects how urgent this looks. The body says "in a handful it does not guard at all". Two candidates were sampled — adminCategories.ts:128 and admin.ts:380 — and both carry a proper if (!rows.length) return 404 a few lines above. The unguarded-index risk is therefore unproven rather than established, and finding out whether any genuinely exist is part of the work here rather than a premise of it.
The missing dependency now exists: **#159**, *Query results are untyped, so `strict: true` stops at the database boundary*.
This issue depends on that one, not on #65. #65 is closed and was *Import convention and avoidable `any`* — it had no stages, and the two things it delivered in this area were `CustomerRow` and the `PoolClient` annotations in `cartCheckout.ts`. The "stage 3" this issue waits for was never part of it.
The sequencing argument in the body still holds exactly as written, and #159 records the measurement that backs it: 1 of roughly 184 query sites carries a row type. Until that changes, `rows[0]` is `any`, `any` indexes to `any`, and `noUncheckedIndexedAccess` would report a count that means little and that changes completely once the rows are typed.
One correction to this issue's own framing, repeated from an earlier comment because it affects how urgent this looks. The body says "in a handful it does not guard at all". Two candidates were sampled — `adminCategories.ts:128` and `admin.ts:380` — and both carry a proper `if (!rows.length) return 404` a few lines above. The unguarded-index risk is therefore unproven rather than established, and finding out whether any genuinely exist is part of the work here rather than a premise of it.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Both workspaces set
strict: true, which is most of the value. The compiler flag that would catch the most remaining real defects here is not instrict, and has to be asked for.The pattern it catches, which this codebase uses everywhere
rows[0].id— indexing an array and immediately reading a property. TypeScript typesrows[0]as the element type regardless of whether the array has any elements, so the compiler is satisfied by code that throws at runtime.noUncheckedIndexedAccesstypes it asT | undefinedinstead and demands the check.The backend does this after a
rows.lengthguard in most places, which is correct and would satisfy the flag with a small change. In a handful it does not guard at all, and those are the ones worth finding — a query that returns nothing where the code assumed a row producesCannot read properties of undefined, surfacing as a 500 with no useful message.Why this is sequenced after #65, not before
Right now
pool.query()returnsanyrows, sorows[0].idis already unchecked and the flag has nothing to bite on — it would report little and prove less. #65 stage 3 gives query results real row types. After that,noUncheckedIndexedAccessis what makes those types honest about emptiness.Enabling it first would be measuring the wrong thing and would produce a violation count that changes completely once #65 lands. Doing it in the other order means each change is verifiable on its own.
What else was checked and is already fine
Worth recording so nobody re-investigates:
enumanywhere. The TypeScript team steers away from them for exactly the reasons this codebase already avoids — union types of string literals are used throughout instead, as inItemStatus.document.getElementById('root')!is the standard React entry idiom, andbyEmail.get(row.email)!inserver.tsfollows aseton the same key.strict: trueon both sides, and no// @ts-ignoreor@ts-expect-erroranywhere.The TypeScript hygiene here is good. This one flag is the gap.
Worth deciding
Whether to also take
exactOptionalPropertyTypesandnoImplicitOverridein the same pass. Both are smaller, andexactOptionalPropertyTypesin particular interacts with how optional props are spread into antd components, so it deserves its own measurement rather than being assumed cheap.Verification
The violation count, measured before and after, and the full suite green. Any site the flag exposes as a genuinely unguarded index is a bug fix rather than an annotation, and should be called out as one rather than silenced with a
!.Severity
Low today, and only becomes worth doing after #65. Filed now so the sequencing is recorded rather than rediscovered.
Found during a TypeScript best-practices review.
Still relevant, but the dependency this issue is waiting on has gone stale and needs re-pointing before the work can start.
This issue holds itself back until "#65 stage 3 gives query results real row types". #65 is closed — but #65 turned out to be Import convention and avoidable
any, which has no stages and never included a typing programme for query results. What it did deliver in this area was narrow:publicCustomer(c: any)became aCustomerRow, and thePoolClientannotations incartCheckout.ts.Measured on
mainjust now, the precondition is not met:pool.query<T>(...)with a row type: 1pool.query(...)total: 89rows[0]reads: 36So 88 of 89 queries still return
anyrows, and this issue's own reasoning still applies unchanged —noUncheckedIndexedAccesswould have almost nothing to bite on, would report a count that means little, and that count would change completely once the rows are actually typed.The blocker is real; it is just no longer represented by an open issue. Typing the query results needs an issue of its own, and this one should depend on that instead of on #65. Filing it is the next step here — this issue should stay open and stay parked until then rather than being started.
Nothing else about the analysis has drifted. The
enumand non-null-assertion findings recorded above still hold: noenumanywhere, and the same two defensible!uses.The missing dependency now exists: #159, Query results are untyped, so
strict: truestops at the database boundary.This issue depends on that one, not on #65. #65 is closed and was Import convention and avoidable
any— it had no stages, and the two things it delivered in this area wereCustomerRowand thePoolClientannotations incartCheckout.ts. The "stage 3" this issue waits for was never part of it.The sequencing argument in the body still holds exactly as written, and #159 records the measurement that backs it: 1 of roughly 184 query sites carries a row type. Until that changes,
rows[0]isany,anyindexes toany, andnoUncheckedIndexedAccesswould report a count that means little and that changes completely once the rows are typed.One correction to this issue's own framing, repeated from an earlier comment because it affects how urgent this looks. The body says "in a handful it does not guard at all". Two candidates were sampled —
adminCategories.ts:128andadmin.ts:380— and both carry a properif (!rows.length) return 404a few lines above. The unguarded-index risk is therefore unproven rather than established, and finding out whether any genuinely exist is part of the work here rather than a premise of it.