Skip to content

Fix single-value property traversals returning arrays#45

Merged
flyon merged 10 commits intodevfrom
claude/fix-single-value-property-fjieZ
Apr 2, 2026
Merged

Fix single-value property traversals returning arrays#45
flyon merged 10 commits intodevfrom
claude/fix-single-value-property-fjieZ

Conversation

@flyon
Copy link
Copy Markdown
Contributor

@flyon flyon commented Apr 2, 2026

Summary

Properties decorated with @objectProperty({maxCount: 1}) (e.g., bestFriend) now correctly return a single ResultRow (or null when absent) instead of ResultRow[] when accessed via traversal queries like Person.select(p => p.bestFriend.name).

Root Cause

The maxCount metadata from PropertyShape was never propagated through the IR pipeline. IRTraversePattern had no maxCount field, so the result mapping layer had no way to distinguish single-value from multi-value traversals, causing all nested groups to be returned as arrays.

Key Changes

  • IntermediateRepresentation.ts: Added optional maxCount?: number field to IRTraversePattern
  • IRDesugar.ts: Propagated maxCount from PropertyShape through DesugaredPropertyStep in segmentsToSteps() and desugarEntry()
  • IRLower.ts: Extended getOrCreateTraversal() and PathLoweringOptions.resolveTraversal signatures to accept and apply maxCount to traverse patterns
  • IRProjection.ts: Updated ProjectionPathLoweringOptions.resolveTraversal signature to forward maxCount from desugared steps
  • resultMapping.ts:
    • Added maxCount?: number to NestedGroup type
    • Propagated maxCount through buildAliasChain(), insertIntoTree(), and buildNestingDescriptor()
    • Added assignNestedGroupValue() helper that unwraps single-value properties (where maxCount <= 1) from arrays to a single ResultRow or null
  • Test fixtures & tests: Added comprehensive test coverage including 4 new single-value property tests and 1 golden snapshot test verifying maxCount propagation through the full pipeline

Implementation Details

  • maxCount is propagated as an optional field through each layer of the IR pipeline, maintaining backward compatibility
  • The unwrapping logic is applied only during result mapping post-processing, not during query building—single-value and multi-value properties generate identical SPARQL patterns
  • Absent single-value traversals return null (consistent with singleResult behavior), not undefined or empty arrays
  • Multi-value properties without maxCount constraints continue to return arrays as before

Breaking Change

Code accessing single-value traversal results as arrays (e.g., result.bestFriend[0]) must be updated to access the value directly (e.g., result.bestFriend).

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1

flyon and others added 10 commits March 27, 2026 11:22
fix: mutation input safety and custom ID support in SparqlStore
chore: version package for release
chore: version package for release
…le values

Properties with maxCount <= 1 (e.g. bestFriend) were always wrapped in
ResultRow[] arrays after SPARQL result mapping. Now maxCount is propagated
through the IR pipeline (DesugaredPropertyStep → IRTraversePattern →
NestedGroup) and used in result mapping to unwrap single-value traversals
to a single ResultRow or null.

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Adds a selectBestFriendOnly fixture and an inline snapshot golden test
that asserts the bestFriend traverse pattern carries maxCount: 1 from
PropertyShape through desugar/lower to the final IRSelectQuery.

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
- Changeset: patch bump for @_linked/core documenting the behavioral change
- Report: docs/reports/012-fix-single-value-property-result.md with full
  architecture, file inventory, test coverage, and known gap

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
@flyon flyon changed the base branch from main to dev April 2, 2026 07:32
@flyon flyon merged commit 4c6b47b into dev Apr 2, 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.

2 participants