Skip to content

refactor(pkg-deps): consume typed dependency-graph fields, drop GenericJSON decoder - #22

Merged
jlitola merged 1 commit into
mainfrom
refactor/pkg-deps-typed-migration
Apr 21, 2026
Merged

refactor(pkg-deps): consume typed dependency-graph fields, drop GenericJSON decoder#22
jlitola merged 1 commit into
mainfrom
refactor/pkg-deps-typed-migration

Conversation

@jlitola

@jlitola jlitola commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates package_dependencies off the backend's deprecated GenericJSON fields and onto their typed replacements. Defensive upgrade — the legacy fields are @deprecated but still served, so this is safe to land ahead of removal.

Backend provides typed siblings for all four legacy fields:

Legacy (GenericJSON) Typed replacement
transitive.dag transitive.dependencyGraph ({formatVersion, nodes, edges} with indexed edges)
transitive.conflicts transitive.dependencyConflicts (importer identity fixed — no more from: \"npm\" bug)
transitive.circularDependencies transitive.circularDependencyCycles (pre-joined displayChain)
dependencyGroups.environmentConstraints dependencyGroups.environmentMarkers ({type, value, raw})

Changes

Service layer (package-intelligence-service.ts):

  • New exported types: DependencyGraph / DependencyGraphNode / DependencyGraphEdge, DependencyConflict / DependencyConflictEdge, CircularDependencyCycle, EnvironmentMarker.
  • TransitiveDependencySummary drops the four GenericJSON fields, gains typed ones. DependencyGroupsInfo.environmentConstraintsenvironmentMarkers.
  • PACKAGE_DEPENDENCIES_QUERY selects typed fields only.
  • UntypedGenericJSON type retained for ChangelogEntryDetail.metadata (the one remaining passthrough).

Envelope builder (package-dependencies-response.ts):

  • Loses ~100 lines of decodeDag / decodeNodes / decodeEdges / decodeObjectNode + tuple-vs-object format tolerance. Replaced with straight field access on the typed graph.
  • buildDirectVersionLookup and buildTransitivePackages walk the typed graph. fromIndex: null (synthetic-root edges) handled explicitly so version resolution still works on packages with synthetic-root DAGs.
  • buildTypedConflicts / decodeConflictEntryForEnvelope / buildTypedCycles / decodeCycleEntryForEnvelope + raw-fallback machinery collapse to straight .map() calls.

Terminal formatter:

  • formatConflictsAndCycles: drops the isTypedConflictArray / isTypedCycleArray guards and JSON-blob fallbacks. Conflict table + cycle chain rendering behaviour preserved.
  • groups.environmentMarkers under --verbose upgrades from JSON.stringify per marker to type: value one-liners (e.g. `extra: async`, `python_version: >= 3.8`).

Public envelope shape

Preserved where possible, one rename:

  • transitive.conflicts: {name, requiredVersions}[]unchanged.
  • transitive.circularDependencies: {cycle: string[]}[]unchanged.
  • groups.environmentConstraints: UntypedGenericJSON[]renamed to groups.environmentMarkers: {type?, value?, raw?}[]. Clean break — the rename matches the typed concept and the backend field name; consumers branch on the presence of the new field.

The typed dependencyGraph sits on the service-level result for a future pkg deps-dag command; this tool's envelope deliberately doesn't surface it, consistent with the existing design commitment.

Testing

  • 1326 tests pass (bun test).
  • Test fixtures migrated from legacy tuple/object DAG shapes to typed form.
  • Raw-fallback / garbage-shape tolerance tests removed — no longer reachable under typed-only input.
  • Typecheck / lint / build clean.

Live-smoke (production pkgseer)

  • pkg deps npm:express --transitive --verbose — 28 direct + 67 transitive packages resolved; per-package importer provenance renders correctly (- ^2.0.0 required by express@5.2.1).
  • pkg deps npm:jest --transitive --verbose — all 16 known conflicts render in the conflicts table (ansi-regex: ^5.0.1, ^6.2.2, …).
  • pkg deps pypi:requests --groups --verbose — PyPI extras (socks, use-chardet-on-py3) render with (optional, extra) tails.
  • --json: runtime.items[].version resolves from the typed graph; transitive.dependencyGraph and legacy dag both absent from the envelope.

Test plan

  • bun test passes (1326 / 0)
  • bun run typecheck / bun run build / bun run lint clean
  • Live-smoke against production pkgseer on npm / PyPI (conflicts, cycles, extras)
  • Legacy GenericJSON fields no longer referenced from source or tests

🤖 Generated with Claude Code

…icJSON decoder

The backend moved `packageDependencies.dependencies.transitive` from
four `GenericJSON` fields (`dag`, `conflicts`, `circularDependencies`,
`environmentConstraints`) to typed siblings (`dependencyGraph`,
`dependencyConflicts`, `circularDependencyCycles`,
`environmentMarkers`). The legacy fields are `@deprecated` but still
served. This PR migrates the client to the typed surface ahead of
removal, swapping the input source without changing the public
envelope contract.

Summary:

- Service layer reads the typed fields. `TransitiveDependencySummary`
  loses the four GenericJSON fields, gains typed ones; new types
  exported: `DependencyGraph` / `DependencyGraphNode` /
  `DependencyGraphEdge`, `DependencyConflict` /
  `DependencyConflictEdge`, `CircularDependencyCycle`,
  `EnvironmentMarker`.
- `package-dependencies-response.ts` loses ~100 lines of
  `decodeDag` / `decodeNodes` / `decodeEdges` / `decodeObjectNode`
  plus the tuple-vs-object tolerance. Direct-version lookup and
  importer provenance walk the typed `DependencyGraph` directly.
- `buildTypedConflicts` / `decodeConflictEntryForEnvelope` /
  `buildTypedCycles` / `decodeCycleEntryForEnvelope` collapse to
  straight `.map()` calls. No raw-fallback path — backend contract
  is typed by construction.
- `groups.environmentConstraints: UntypedGenericJSON[]` (the last
  raw passthrough in the envelope) becomes `environmentMarkers:
  [{type, value, raw}]`. Verbose-terminal output upgrades from
  `JSON.stringify` per marker to `type: value` one-liners.
- `formatConflictsAndCycles`: drops the `isTypedConflictArray` /
  `isTypedCycleArray` guards and their JSON-blob fallbacks. Conflict
  table + cycle chain rendering behaviour is preserved.

The public envelope shape is unchanged for `transitive.conflicts`
(`{name, requiredVersions}[]`) and `transitive.circularDependencies`
(`{cycle: string[]}[]`). `groups.environmentConstraints` renames to
`groups.environmentMarkers` with a typed content shape — clean break
over ambiguity; consumers branch on the presence of the typed
fields.

The typed `dependencyGraph` sits on the service-level result for a
future `pkg deps-dag` command; this tool's envelope deliberately
doesn't surface it.

Also:

- `--lifecycle` was already on the wire (shipped previously), so no
  follow-on change needed there.
- `UntypedGenericJSON` type is retained — still used by
  `ChangelogEntryDetail.metadata`.

Testing:
- 1326 tests pass. Test fixtures migrated from the legacy
  tuple/object DAG shapes to the typed form; raw-fallback /
  garbage-shape tolerance tests removed.
- Typecheck / lint / build clean.

Live-smoke:
- `pkg deps npm:express --transitive --verbose` — 28 direct + 67
  transitive resolved; importer provenance renders correctly.
- `pkg deps npm:jest --transitive --verbose` — all 16 known
  conflicts render in the conflicts table with correct
  `name: range1, range2, …` formatting.
- `pkg deps pypi:requests --groups --verbose` — PyPI extras
  (`socks`, `use-chardet-on-py3`) render with `(optional, extra)`
  tails.
- `--json` envelope: `runtime.items[].version` resolves,
  `transitive.dependencyGraph` and legacy `dag` both absent.
@jlitola
jlitola merged commit 4b82792 into main Apr 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant