docs(schema): correct from+to relationship cardinality to many-to-one#602
docs(schema): correct from+to relationship cardinality to many-to-one#602kriszyp wants to merge 2 commits into
Conversation
The @relationship(from:, to:) example joined two scalar attributes (productSku -> sku) but was labeled "useful for many-to-many relationships" — cardinality in Harper is decided by whether the target `to` attribute is an array (search.ts's isManyToMany check), so a scalar-to-scalar join is many-to-one, not many-to-many. The other bug in issue #582 (querying.md resellers example pointing `from` at the singular resellerId) was already fixed on main by 7e373fa before this dispatch ran. Refs #582 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the documentation in reference/database/schema.md to clarify the cardinality rules for foreign key to foreign key relationships using @relationship(from: attribute, to: attribute). The review feedback correctly identifies an inaccuracy in the explanation of how cardinality is determined and provides a clearer, simplified phrasing to resolve the issue.
🚀 Preview DeploymentYour preview deployment is ready! 🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-602 This preview will update automatically when you push new commits. |
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
🚀 Preview DeploymentYour preview deployment is ready! 🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-602 This preview will update automatically when you push new commits. |
| orderId: Long @indexed | ||
| productSku: Long @indexed | ||
| product: Product @relationship(from: productSku, to: sku) # join on sku, not primary key | ||
| product: Product @relationship(from: productSku, to: sku) # many-to-one join on sku, not primary key |
There was a problem hiding this comment.
High: the corrected example does not actually work in Harper
Relabeling the join as many-to-one is directionally right for the intended cardinality, but the schema as written doesn't resolve at all on current main (513f87471), regardless of label. Table.ts's attribute-wiring (updatedAttributes(), around resources/Table.ts:4414-4450) is if (relationship.to) { if (attribute.elements?.definition) {...} } else if (relationship.from) {...}. Since to: sku is set, it takes the first branch unconditionally — and because product: Product is a scalar field (no attribute.elements), that branch's else fires: console.error('...must have an array type referencing a table as the elements'), and attribute.resolve is left null. The else if (relationship.from) branch — the one that actually knows how to resolve a single scalar relationship — is never reached when to is also set.
I verified this empirically against a live instance built from origin/main with this exact schema: GET /OrderItem/:id (no select) silently omits product from the response entirely (not even null), and GET /OrderItem/:id?select(...,product{...}) (the standard way to expand a relationship, as used elsewhere in these docs/tests) 500s with TypeError: Cannot read properties of undefined (reading 'propertyResolvers') in Resource.ts (transformForSelect), because it dereferences attribute.resolve.definition on a null resolve.
The combined from+to resolver path only registers when the field type is an array (attribute.elements?.definition) — there is no code path that resolves a scalar foreign-key-to-foreign-key relationship by a non-primary-key to attribute. So the fix that actually makes this example work is products: [Product] @relationship(from: productSku, to: sku) (array field), not a comment/prose relabel — and once it's array-typed, it's structurally a one-to-many/many-to-many resolver in the code regardless of how unique sku is in practice.
This is a pre-existing bug in the example (not introduced by this PR), but the PR's stated purpose is specifically to make this example accurate, and its test plan only checked search.ts's isManyToMany (the query-filter cardinality calc), not Table.ts's resolver wiring (the code path that determines whether the field resolves at all). Worth fixing before merge so the docs don't keep shipping a schema that 500s when a reader follows it.
Suggested fix: change the field to an array type (products: [Product] @relationship(from: productSku, to: sku)) and update the surrounding prose (line 400) accordingly, or drop the to argument and this section's non-primary-key framing until Harper actually supports scalar-target foreign-key-to-foreign-key resolution.
—
Generated by Barber AI
What
Fixes the second bug from #582: the
@relationship(from: attribute, to: attribute)example inreference/database/schema.mdjoins two scalar fields (productSku->sku) but was described as "useful for many-to-many relationships." Reworded the section to state that cardinality follows thetoattribute (scalar -> many-to-one, array -> many-to-many via the array pattern already shown in thefrom-only section above), and relabeled the example's inline comment as many-to-one.Why
Per #582: Harper's
search.tsdecides many-to-many purely by whether the targettoattribute is an array (isManyToMany = Boolean(findAttribute(relatedTable.attributes, attribute.relationship.to)?.elements)). A scalar-to-scalarfrom+tojoin can never be many-to-many, so the old text mislabeled the example and could mislead readers (and downstream agent-rules generation inHarperFast/skills) into modeling it wrong.The issue's first finding (
reference/rest/querying.mdpointingfromat singularresellerIdinstead ofresellerIds) was already fixed onmainby 7e373fa prior to this dispatch — no change needed here.Test plan
npx prettier --check reference/database/schema.md reference/rest/querying.md— passes~/dev/harper'ssearch.tsisManyToManylogic per the issue's citationRefs #582
🤖 Generated with Claude Code