docs: bless the annotated self-referencing deriver idiom - #65
Merged
Conversation
Two claims in the #60 documentation were wrong on remeasurement: both faces of the annotation check report TS2322 on the deriver's return expression (not one at the deriver body and one at the Entity.computed call), and the WrongBody test-d pin disagreed on both faces at once, making its single @ts-expect-error vacuous. Also adds negative pins for the unannotated form on Entity.abstract roots and .extend variants (base.ts is a different builder than entity.ts), clarifies the changeset wording (Entity.invariant takes a predicate, not an "invariants deriver"), and notes the change is documentation only.
There was a problem hiding this comment.
Pull request overview
Documents and type-checks the “self-referencing deriver” idiom: computed derivers and Entity.invariant predicates may call the declaring entity’s own statics if the function has an explicit return-type annotation, avoiding the TS2506/TS7024 circularity triggered by inferred return types in the heritage clause. This is positioned as a documentation + regression-guard change with no runtime/API behavior change (closes #60).
Changes:
- Added a new
.test-d.tsfile that pins the working annotated pattern and the known dead ends (TS2506,TS7024,TS2502) across entity, root, and variant cases. - Expanded JSDoc in
computedandinvariantto describe the pattern and point to the regression test. - Updated docs (explanation + reference) and added a patch changeset to ship the JSDoc updates.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/entity/src/computed.ts | Adds JSDoc guidance describing the annotated self-static deriver idiom and why it avoids TS2506. |
| packages/entity/src/invariant.ts | Adds parallel JSDoc guidance for invariant predicates calling the entity’s own statics. |
| packages/entity/src/computed.test-d.ts | New type-level regression guard covering the annotated success case, wrong-annotation failures, and dead-end patterns. |
| docs/explanation/computed-fields.md | Adds an explanation section documenting the pattern, error codes, and boundaries (root-static vs self-static, this dead end). |
| docs/reference/declaration.md | Adds brief reference-level pointers for both computed and invariant to the explanation section. |
| .changeset/computed-self-reference-annotation.md | Patch changeset documenting the user-facing JSDoc/doc update (no runtime/API changes). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #60.
A
computedderiver or anEntity.invariantpredicate can reference the entity's own statics. The missing piece was an explicit return-type annotation on the deriver, not a library change — so this branch documents and pins the idiom, and changes no API.What was measured
On TypeScript 7.0.2 (repo) and 5.9.3 (the
typescript-consumerfixture version):Doc.isActiveneeds the class type mid-resolution. The annotation preempts that inference; the body itself is checked later, once the class type is settled. Unannotated isTS2506on the class plusTS7024on the arrow, and it cascades — one cause produced seven errors, sincemake/input/output/createInput/updateInputall vanish with the poisoned base expression.NoInfer<z.input<T>>, anunknownreturn position, and a curried shape all still produceTS2506on a minimal model. The annotation is the only lever and it lives at the call site.TS2322on the deriver's return expression: a body disagreeing with the annotation, and an annotation disagreeing with the schema (or widened tounknown).dalso stays contextually typed — an undeclared field is still an error.thisis a hard dead end. Athis: typeof Docparameter is signature position, never deferred, so it is circular even with the return annotated (TS2502) — and the library cannot supply that type either, because the statics live in a class body TypeScript has not yet formed.Changes
packages/entity/src/computed.test-d.ts(new) — pins the idiom on concrete entities,Entity.abstractroots and.extendvariants; both wrong-annotation faces isolated one error each;dstill typed; and a dead-end ledger for the unannotated deriver, the unannotated predicate, the unannotated root and variant paths, and thethisparameter. Every@ts-expect-errorwas verified to be consumed at its exact placement, on both compilers.packages/entity/src/computed.ts,invariant.ts— one JSDoc paragraph each.docs/explanation/computed-fields.md— a## Self-referencing deriverssection quotingTS2506verbatim so the error is searchable into the docs;docs/reference/declaration.mdgets two pointers..d.ts.examples/billing-domainis deliberately untouched: the options object is erased from the emitted.d.ts, so a self-referencing computed there would guard nothing.Gate
format --check,lint,typecheck(both workspaces, including the 7.0.2 + 5.9.3 double-compile and the.emit-checkpass),test(207),knip,build— all green.