Skip to content

Dev#46

Merged
flyon merged 30 commits intomainfrom
dev
Apr 3, 2026
Merged

Dev#46
flyon merged 30 commits intomainfrom
dev

Conversation

@flyon
Copy link
Copy Markdown
Contributor

@flyon flyon commented Apr 2, 2026

No description provided.

github-actions Bot and others added 30 commits March 28, 2026 23:48
…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
…fjieZ

Fix single-value property traversals returning arrays
Update 3 Fuseki tests that expected bestFriend to be ResultRow[] —
now it's a single ResultRow since bestFriend has maxCount: 1.

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
1. selectFriends: assert p1.friends is an array of 2 entity references
   (p2 and p3), not just "defined and not null". This may expose a bug
   in flat multi-value projection (currently takes first binding only).

2. doubleNestedSubSelect: validate full nested structure —
   friends[0].bestFriend.name === 'Jinx' (3-level nesting).

3. nestedObjectProperty: assert exact count (1, not >=1), verify
   friends array contents, and check bestFriend is unwrapped single
   ResultRow with id containing 'p3'.

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Tests now assert actual values instead of just checking existence:

- selectNestedFriendsName: verify 2-level nesting structure and friend names
- selectDeepNested: assert empty result (chain is impossible with test data)
- selectMultiplePaths: verify name, bestFriend unwrap, friends array
- nestedObjectPropertySingle: match nestedObjectProperty assertions
- subSelectAllProperties: verify friend count and property values
- subSelectAllPropertiesSingle: verify bestFriend unwrap with all properties
- nestedQueries2: verify friends array, firstPet ref, bestFriend unwrap
- subSelectArray: verify friend count, names, and hobby values
- selectShapeSetAs/selectShapeAs: verify guardDogLevel values
- countNestedFriends/countLabel: verify actual count values
- preloadBestFriend: verify bestFriend unwrap with preloaded name

Two tests will fail due to flat multi-value projection bug:
- selectFriends (already flagged in prior commit)
- selectMultiplePaths (friends flat field takes first binding only)

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Properties without maxCount constraints (e.g. `friends`) were returning
a single entity reference instead of an array when selected via flat
projections like `Person.select(p => p.friends)`.

Root cause: `mapFlatRows` deduplicated by root ID and took only the first
binding. `mapNestedRows` used `groupBindings[0]` for root-level flat fields.

Fix:
- Add `maxCount?: number` to `IRPropertyExpression` (propagated from
  `DesugaredPropertyStep.maxCount` in `IRProjection.ts`)
- Add `maxCount?: number` to `FieldDescriptor` in result mapping
- Refactor `mapFlatRows` to group bindings by root ID, then:
  - Single-value fields (maxCount <= 1): take first binding (unchanged)
  - Multi-value fields (no maxCount): collect all distinct values into arrays
- Update `mapNestedRows` to use same logic for root-level flat fields
- Add 5 new unit tests covering collection, dedup, absent, mixed, and
  multi-value-in-nested-mode scenarios

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
- Consolidate duplicated extraction logic: populateFields now delegates
  to extractFieldValue instead of inlining the same URI/literal handling
- Simplify extractFieldValue: merge redundant URI branches (both returned
  the same {id: val.value})
- Add clarifying comments on FieldDescriptor.maxCount, extractFieldValue
  URI handling, and multi-value collection semantics
- Write report 013 (flat multi-value projection fix)
- Update report 012 with follow-on reference
- Remove plan/ideation docs (013)
- Add changeset for flat multi-value fix (patch)

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Multi-value literal properties (e.g. `nickNames: string[]`) were silently
dropped because `populateFlatFields` filtered collected values with
`typeof extracted === 'object' && 'id' in extracted`, which rejects
string/number/boolean/Date values.

Fix:
- Remove the object/id guard — collect all non-null values
- Add `string[] | number[] | boolean[] | Date[]` to `ResultFieldValue`
  type to support literal arrays
- Add 4 new unit tests: literal string collection, mixed URI+literal,
  absent (empty array), and deduplication

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
…ngeset

- Merge reports 012 + 013 into single report 012 covering all maxCount
  result mapping fixes (single-value, multi-value URI, multi-value literal)
- Remove known limitation about literal fields (now fixed)
- Remove plan/ideation docs for 014 (flat multi-value literal fix)
- Merge two changesets into one unified changeset

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
…fjieZ

Fix maxCount-aware result mapping for single-value and multi-value properties
1. nestedObjectProperty/nestedObjectPropertySingle: bestFriend is OPTIONAL
   within the friends traversal (not INNER JOIN), so both p1 and p2
   qualify. Fixed assertions to expect 2 rows with null bestFriend
   for friends that lack one.

2. Aggregate expressions (countFriends, countNestedFriends, countLabel,
   customResultNumFriends): non-property expressions like aggregate_expr
   had no maxCount, so populateFlatFields treated them as multi-value
   and wrapped scalar results in arrays. Fixed by defaulting all
   non-property_expr expressions to maxCount: 1 in buildNestingDescriptor.

3. countNestedFriends/countLabel: relaxed assertions that assumed specific
   count values without Fuseki verification — now check structural
   correctness (numeric type, row count >= 2).

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Root cause: GROUP BY queries with traversals (e.g. friends.friends.size())
produce both a nested group (empty friends array from aggregated-away ?a1)
AND the aggregate count as flat fields. The old Object.keys().find(k !== 'id')
hit the empty friends array first, returning [] instead of the count.

Fix: add findCountValue() helper that searches for the first numeric-valued
key, skipping non-numeric fields like the empty nested group array. Restore
strict assertions: p1 count=2, p2 count=0.

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Fix count aggregation tests and aggregate wrapping bug
Root cause: irToAlgebra renames aggregate aliases that collide with
traversal aliases (e.g. a1 → a1_agg) and updates resultMap, but not
the projection. buildNestingDescriptor then fails to find the projection
entry for the renamed alias, silently dropping the aggregate field.

Fix: when a resultMap entry has no matching projection (renamed by
irToAlgebra), create a flat field descriptor using the resultMap alias
directly as the SPARQL variable name, with maxCount: 1.

Adds unit test simulating the exact alias rename scenario.

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Fix aggregate count mapping when alias collides with traversal
- Add ci.yml: runs build+test on PRs targeting main/dev (previously
  tests only ran after merge, allowing broken code through)
- Bump changeset from patch to minor: result shape changes are
  breaking for existing consumers

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Tests now run on PRs via ci.yml. The post-merge publish workflow
only needs to build before publishing — testing is redundant since
the code was already tested before merge.

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Add PR CI workflow and bump changeset to minor
npm install -g npm@^11.5.1 fails on Node 22.22.2 with
MODULE_NOT_FOUND for promise-retry. Node 22 ships with npm 10.x
which works fine for changeset publish.

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Fix publish workflow: remove broken npm global upgrade
Node 22.22.2 (latest on GitHub runners) breaks npm install -g npm@^11.5.1
with MODULE_NOT_FOUND. Fix: pin Node to 22.16.0 LTS across all workflows.
Restore npm 11 upgrade in release/dev-release jobs (needed for publish).

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Pin Node to 22.16.0 LTS and restore npm 11 for publish
Both release and dev-release jobs already do their own
checkout/install/build. The standalone build job was just
wasting CI minutes as a redundant gate.

https://claude.ai/code/session_017mqanCkMvA1VU8MVD7hkA1
Remove redundant standalone build job from publish workflow
@flyon flyon merged commit 9037bc5 into main Apr 3, 2026
7 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