Uh oh!
There was an error while loading. Please reload this page.
docs: bless the annotated self-referencing deriver idiom - #65
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.
Uh oh!
There was an error while loading. Please reload this page.
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.