Uh oh!
There was an error while loading. Please reload this page.
refactor(rest): compile the non-door getMetaItems request literals against the declared contract (#9805) - #9845
Conversation
…ainst the declared contract (#9805) Nine getMetaItems call sites in packages/rest/src/rest-server.ts outside the four meta-read doors passed their request through `as any` (or through a `p: any` parameter), so the compiler checked nothing about them. Every member they thread has been expressible in declared types since #9741 landed `previewDrafts` on GetMetaItemsRequest and the TransportScopedMetaRequest envelope for the transport-level environmentId. Each literal is now a named const typed TransportScopedMetaRequest of GetMetaItemsRequest (or plain GetMetaItemsRequest where the site threads no environmentId), the same shape #9741 gave the doors. No behaviour change: the outgoing payloads are byte-identical (same keys, same conditional spreads). The optional-call spelling, the typeof-function guards and the runtime-shaped result handling all deliberately survive — retiring any of them would change behaviour rather than typing — and the envelope alias now documents why. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WeN7F6jQFpcqW2BN56RdPa
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not seeCoarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 176e45388c9d17cfe5f8f4a74413154422dca440 && git checkout 176e45388c9d17cfe5f8f4a74413154422dca440
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e77a9b45e4ea3a0a9960446f52f0e6689ce3a9e4 a98b6834e33335243131ff7c908a09ba7487e89f && git checkout -B drift-repro e77a9b45e4ea3a0a9960446f52f0e6689ce3a9e4 && git merge --no-ff a98b6834e33335243131ff7c908a09ba7487e89f
node scripts/docs-audit/affected-docs.mjs --json e77a9b45e4ea3a0a9960446f52f0e6689ce3a9e4 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9805
Behaviour-preserving type-tightening in
packages/rest/src/rest-server.ts: the non-doorgetMetaItemscall sites now compile their request literal against the declared spec contract instead of smuggling it past the compiler withas any.The value is not tidiness. An un-typed request literal is exactly the blindness class that lets a future key drift silently — restoring compiler coverage over a request that is fully typeable today is the invariant being restored.
The residue, re-derived on the merged ref (not read off the card)
#9741 (PR #9804, merged
2a29caa532) landed after the card was written, so the card's list is a clue. Re-derived atorigin/main152bff8fcd. Population: 25getMetaItemsmentions in the file, placed exhaustively:typeof … === 'function'feature-detect reads:4022):3438,:3479)The three other #9741 doors (
getMetaItem/Cached/Layered) are different verbs and sit outside this population entirely. They are untouched.The nine sites
Each request literal became a named const typed
TransportScopedMetaRequest< GetMetaItemsRequest >— #9741's envelope, which declares the transport-levelenvironmentIdon top of the spec shape — or plainGetMetaItemsRequestat the one site that threads noenvironmentId(the same split #9741 used for its uncached door).:1332loadObjectItems(API-exposure gate)type,environmentId?:1715fetchAudienceBooks— alsop: any→RestProtocoltype,environmentId?:4557type,packageId?,environmentId?:4586type,packageId?,environmentId?:5138type,environmentId?:8031resolveFormBySlugview listingtype,environmentId?:8095type,environmentId?:8411type,environmentId?:8614type,previewDraftsEvery member is declared:
type/packageId/organizationId/previewDraftsonGetMetaItemsRequest(the last one is #9741's), plus the envelope'senvironmentId.packages/specneeded no change — no new or widened declared key was required at any site.Two card-named sites delivered no diff, and the measurement is why
:3438(discovery{object}expansion) and:3479(theapilisting) areprotocol?.getMetaItems?.({ … })whereprotocolis typedRestProtocol | undefined. Their request literals are already compiler-checked — the card read the visibleas anythere, but that cast is on the result, not the request. There is no request blindness left to retire, and the only remaininganyat those sites is the response-shape tolerance described below. Delivering a diff there would remove a visibleanywhile the shape stayed untypeable — the case the dispatch brief said to name rather than do.Conversely six sites the card did not name (
:4557,:4586,:5138,:8031,:8095,:8411) carried the same defect and are fixed here.What deliberately survives, and why each would be a behaviour change
Documented on the
TransportScopedMetaRequestalias so the next reader does not "finish the job":getMetaItems?.(…)) and thetypeof … === 'function'guards.getMetaItemsis a requiredMetadataProtocolmember, so the card is right that these are not feature detection in the type sense — butmetaTypeIsLive's own comment records the measured reason they exist: a host may occupy the protocol slot with an object that does not implement the whole surface. Retiring one converts a tolerated absence into aTypeErrorat runtime.{ type, items }while these sites also tolerate the bare-array shape older hosts and stubs return (metaItemsArray). The response stays runtime-shaped on purpose; only the request is typeable today, and only the request is typed.getMetaDiagnostics,listDrafts,migrateStoredMetadata, …) — untouched. Runtime casts are the documented convention there, and tightening one would turn optional capability detection into a hard dependency, silently at compile time.Zero runtime change. Each edit hoists a literal into a const and drops a type-level cast; outgoing payloads are byte-identical (same keys, same conditional spreads), and
as anyerases at emit.Verification — all at head
a98b6834e3pnpm --filter @objectstack/rest typecheck(tsc --noEmit) — clean, real exit 0.pnpm --filter @objectstack/rest test(vitest run) — 129 files / 2105 tests passed, real exit 0 read unpiped (a redirect, not a pipe, so no masked teardown code).bogusKey: 1into theloadObjectItemsliteral →TS2353, and the printed type resolves the declared shape out of the rebuilt.d.ts:TransportScopedMetaRequest< { type: string; packageId?: string | undefined; organizationId?: string | undefined; previewDrafts?: boolean | undefined; } >—previewDraftspresent proves it is reading [finding] Meta-read request schemas still omit previewDrafts / state / environmentId, so the REST call-site casts survive the organizationId catch-up #9741's landed declaration, not a stale build. InjectingpreviewDrafts: 'yes'into the dataset literal →TS2322: Type 'string' is not assignable to type 'boolean | undefined'. Restored from the commit (never fromorigin/main), re-typechecked green.pnpm --filter '@objectstack/rest^...' build— green.node scripts/pm/dispatch-gates.mjs, no hand-fed paths) at the final head: all 11 path-matched families green —check:authz-resolver,check:changeset-gate-self-tests,check:cross-package-test-inputs,check:dispatcher-error-vocabulary,check:objectui-changeset,check:route-envelope,check-adr-0087-registration,check-changeset-no-major,check-cross-package-test-inputs,check-empty-changeset,check-affected-docs. Plus the three the derivation is known not to name:pnpm lint(eslint . --no-inline-config) green,check:slot-lookupgreen,check:nul-bytesgreen — andcheck:meta-type-normalized, which scans this file, green.check:route-envelopeis a ratchet on this file and its count is unmoved: no envelope call site is added or removed.Changeset
@objectstack/restpatch, stated rather than assumed.skip-changesetwould be wrong here: that label is for a PR that publishes nothing (tests / workflow /.claude/only), and this one edits production source in a published package, so the package does republish — even though there is no behaviour, no wire and no public-type change.TransportScopedMetaRequestis a module-local type andfetchAudienceBooksis private, so no exported surface moves.Generated by Claude Code