feat(r3-b): put every screen behind the repository contract - #63
Open
Morialkar wants to merge 2 commits into
Open
feat(r3-b): put every screen behind the repository contract#63Morialkar wants to merge 2 commits into
Morialkar wants to merge 2 commits into
Conversation
…creens R3-B needs a seam for a local SQLite driver, and there was none: the client called apiClient from 27 files, 96 call sites, with no abstraction in between. D8's working agreement puts storage behind interfaces in packages/core so the shell choice stays reversible; this starts paying that off. packages/core now owns the canonical entity shapes. Table and Database had each been redeclared seven times across routes and components, with fields that had drifted apart. FieldType moves here too, so drivers and UI cannot diverge on the registry's closed set. The contract is deliberately plain promises: query keys, caching and optimistic updates stay in the client. Writing it against the real call sites corrected several guesses: the list endpoint reads sort_dir rather than direction, restore-version is keyed by log_id rather than a version, reassign-links expects to_record_id, CSV import goes through multipart and was missing entirely, and history, trash and referencing-records all answer inside a data envelope. Nine files migrated so far; the remaining eighteen follow.
All 96 call sites across 27 files now go through the contract instead of apiClient, so a SQLite driver can replace the HTTP one without touching a screen. No apiClient call remains outside lib/ and tests. Local entity declarations are gone in favour of packages/core: Table and Database had been written out seven times each, and RecordEntity, ViewConfig, Share, Template and the record value types now have one home. lib/apiTypes re-exports from core rather than redeclaring. Field option normalisation moved into the driver. The API may omit options and validation; the domain type promises them, so the boundary that turns API shape into domain shape is the right place to fill them in, rather than every caller. Two defects surfaced while writing the contract against real call sites. RecordDetailView typed the views endpoint as an envelope and read `viewsQuery.data?.data`, but the endpoint returns a bare array — ViewFeatureTest asserts a root count. Reading `.data` off an array yields undefined, so the list was always empty and no card view ever applied in the detail view. Two unit tests mocked the same envelope, which is why they passed while the feature was broken; their mocks now match the API. Several contract guesses were also wrong and were corrected against the code: restore-version is keyed by log_id, reassign-links expects to_record_id, filters travel as an array rather than a map, template install sends the whole payload rather than an id, and history, trash, referencing-records and map-points all answer inside a data envelope.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First step of R3-B. The epic calls for a local SQLite driver, and there was nowhere to plug one in: the client called
apiClientdirectly from 27 files, 96 call sites, with no abstraction between screens and transport. D8's working agreement puts storage behind interfaces inpackages/coreprecisely so the shell choice stays reversible; this pays that off.What changed
packages/corenow owns the domain.TableandDatabasehad each been redeclared seven times across routes and components, with fields that had drifted apart.RecordEntity,ViewConfig,Share,Template,FieldTypeand the record value types now have one home, andlib/apiTypesre-exports from core instead of redeclaring.The contract is eight repositories — databases, tables, fields, records, views, reports, shares, templates — as plain promises. Query keys, caching and optimistic updates stay in the client; the interfaces are free of TanStack Query concepts so a driver never has to know about them.
httpRepositoryis the hosted driver, injected throughRepositoryProvider. That provider is the swap point: the local-first shells will pass a SQLite-backed driver and no screen will know.Field normalisation moved into the driver. The API may omit
optionsandvalidation; the domain type promises them, so the boundary that turns API shape into domain shape fills them in — rather than each of the callers that used to do it inconsistently.A real bug this surfaced
RecordDetailViewtyped the views endpoint as an envelope and readviewsQuery.data?.data. The endpoint returns a bare array —ViewFeatureTestasserts a root count of two. Reading.dataoff an array givesundefined, so the list was always empty and no card view ever applied in the record detail view.Two unit tests mocked the same envelope, which is exactly why they passed while the feature was broken. Their mocks now match what the API returns.
Contract guesses that were wrong
Writing the interfaces against the real call sites corrected several assumptions I had made from the endpoint names:
directionsort_dirversion_idfor restorelog_idto_recordto_record_idtemplate_iddataCSV import was missing from the contract entirely — it goes through multipart.
Verification
apiClientcalls remain outsidelib/and teststsc -b, ESLint (0 errors), Prettier cleanNot in this PR
The SQLite mirror schema, the local driver, the offline queue and the no-auth local mode — the rest of R3-B. They now have a seam to attach to.