fix: revert ObservableStatus to a strict (non-union) type#750
Conversation
Adds tsconfig.test.json (type-checks src + test) and runs it in the type-check workflow. The test suite already exercises hooks the way consumers do (reading .data off results), so a breaking change to the public types now fails CI at compile time. Also fixes pre-existing test-only type issues surfaced by turning the check on: unused imports, node globals (require/global/Buffer) via the node types, and a FunctionComponent children prop annotation.
4.2.4 shipped ObservableStatus<T> as a discriminated union (data is T | undefined unless narrowed on status), which broke the documented pattern of reading .data directly across every data hook. Revert the type to the previous strict shape (data: T) so this ships as a patch. The genuinely-optional cases stay correctly typed at the hook level (useFirestoreDocData/DocDataOnce return T | undefined, useUser returns User | null, useDatabaseListData returns T[] | null), which is where the undefined belongs rather than on ObservableStatus itself. The internal SuspenseSubject status builders keep an as-cast since the in-flight loading value is internally T | undefined.
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
ObservableStatus is now an interface again rather than a discriminated union type-alias, so the generated reference docs drop the per-status sub-interface pages and retarget links from type-aliases/ to interfaces/.
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
armando-navarro
left a comment
There was a problem hiding this comment.
Nice fix, along with the CI type-check. I verified the whole thing on current main: the revert is faithful to the pre-4.2.4 shape, the red-then-green holds, and types, tests, and docs are all green.
What I verified
- The revert restores
ObservableStatus<T>to the flatdata: Tshape (identical to 4.2.3), and the genuinely-optional cases stay where they belong:useFirestoreDocData/DocDataOnce→T | undefined,useUser→User | null,useDatabaseListData→T[] | null. - Red then green:
tsc -p tsconfig.test.jsonreports exactly 28TS18048at the guard commit (union still in place) and 0 after the revert. The 28 are real.datareads, and the test fixes added noas any/@ts-ignore/!suppressions, so the guard has real teeth. tscis clean onsrcandsrc + test; the non-emulator suites are 21/21; the firestore/database/auth emulator suites are green; and the regenerated reference docs match a cleannpm run docs, so the docs check will pass.
The one decision that isn't mine: semver
Reverting removes three types that 4.2.4 published (ObservableStatusSuccess, ObservableStatusError, ObservableStatusLoading) and re-narrows data. It restores the 4.2.3 contract, so shipping it as a patch to undo a bad patch reads as reasonable to me, but removing exported types is technically breaking, so I would rather Jeff make that call explicitly.
Either way, a changelog line noting those three types are gone would help anyone who adopted them in 4.2.4.
Smaller notes
- The guard is genuine but not exhaustive: the
useFirestoreDocDatatests use<any>, so.datais untyped there, anduseDatabaseObjectData/useDatabaseListDataare not exercised at all, so a future union reintroduction on those would not be caught. One concretely-typed.dataread for doc data plus an object/list test would close the gaps. Non-blocking. - The description says two commits, but there are three (the docs regen is the third, and worth keeping).
Everything code-level checks out, so approving on that, with the semver call left to Jeff.
…eads Per review: the guard did not exercise useDatabaseObjectData, and the existing useFirestoreDocData tests use <any> so their .data reads are untyped. Add a useDatabaseObjectData test with a typed .data read (data is T, so it fails to compile if ObservableStatus regresses to a union) and a concretely-typed useFirestoreDocData read.
|
Thanks Armando! Got both in a new commit: added a useDatabaseObjectData test with a typed .data read (fails to compile if ObservableStatus goes back to a union, confirmed) plus a concretely-typed docData read instead of the ones, no suppressions. Also fixed the commit count and added a "Removed types" note for ObservableStatusSuccess/Error/Loading. Landing as a patch for 4.2.5. |
Final due-diligence: published
|
Summary
4.2.4 shipped
ObservableStatus<T>as a discriminated union (#583), sodatabecameT | undefinedunless narrowed onstatus. That broke the documented pattern of reading.datadirectly (e.g.const { data } = useFirestoreCollectionData(q); data.map(...)) across every data hook, including the suspense pattern, and it went out as a patch.This reverts
ObservableStatusto its previous strict shape (data: T) so the fix ships as a patch, and adds a type-check over the test suite in CI so a regression like this fails at compile time going forward.Commits
test: type-check the test suite in CIaddstsconfig.test.json(type-checkssrc+test) and runs it in the type-check workflow. With the union still in place this turns the check red with 28TS18048"possibly undefined" errors on the existing test assertions. Also fixes pre-existing test-only type issues the check surfaces (unused imports, node globals, achildrenprop annotation).fix: revert ObservableStatus to a strict (non-union) typereverts the type todata: T. The check goes green (0 errors).docs: regenerate reference docsfor theObservableStatustype change (drops the per-status sub-interface pages, retargets links fromtype-aliases/tointerfaces/).test: strengthen the type guardwith a typeduseDatabaseObjectDataread (which fails to compile ifObservableStatusregresses to a union) and a concretely-typeduseFirestoreDocDataread, per review.The existing tests already read
.dataoff hook results the way consumers do, so once type-checked they catch this class of break with no separate type-regression file.What stays correctly typed per hook
The genuinely-optional cases keep their
undefined/nullat the hook level, which is where it belongs:useFirestoreDocData/useFirestoreDocDataOncereturnObservableStatus<T | undefined>(fix: update useFirestoreDocData return type to include undefined #733)useUserreturnsObservableStatus<User | null>useDatabaseListDatareturnsObservableStatus<T[] | null>Reverting the union cleared all 28 possibly-undefined errors, confirming no other hook needed per-hook
undefined.Removed types (for release notes)
Reverting removes the three interfaces 4.2.4 introduced:
ObservableStatusSuccess,ObservableStatusError,ObservableStatusLoading. Anyone who adopted them in 4.2.4 should readdataoffObservableStatus<T>directly again. Worth a changelog line on the 4.2.5 release.Verification
TS18048at commit 1, 0 at commit 2.tsc --noEmitgreen on bothsrcandsrc+test.useObservable,firebaseApp) pass (21/21); emulator suites green in CI.useFirestoreCollectionData(...).data.map(...)pattern understrict, green here, red on 4.2.4.Intended to ship as a patch (4.2.5). Tracks #749.