Skip to content

IDE DX Improvements - #15

Merged
vethman merged 1 commit into
mainfrom
feature/ide-dx-improvements
Oct 6, 2026
Merged

vethman merged 1 commit into
mainfrom
feature/ide-dx-improvements

Conversation

@vethman

@vethman vethman commented Oct 5, 2026

Copy link
Copy Markdown
Member

Summary

This branch makes the auto builders nicer to work with in the editor and deprecates the helper types they made redundant. It holds five changesets, all minor.

Nothing that compiled before stops compiling, except one reserved-name edge case (see Compatibility). The only runtime change is the new removeRequiredProperty method.

What changes

1. Ctrl+Click on a setter opens the property it sets

Go to Definition on builder.city(...) now opens city in your own type or class. Hovering the setter shows the property's doc comment, and renaming the property also renames the setter calls.

How: the setters are now generated by two mapped types instead of one.

  • One keeps every ordinary property under its own name. TypeScript links these setters to their source property.
  • One renames the rare reserved names to …Prop. These stay unlinked.

Setter names, parameter types and union-target behaviour are unchanged. Compiling the test suite now takes roughly half as many type instantiations.

2. Property-name autocomplete in BuilderWith / ClassBuilderWith

An optional third type argument names the builder class. Editors look up the property names there, so the keys autocomplete inside the quotes:

inRotterdam(): BuilderWith<this, "city", AddressBuilder> {
	return this.city("Rotterdam");
}

It defaults to the first argument, so existing two-argument uses are unaffected.

3. A too-early build() names what is missing

The compile-time-checked build variants now check the builder they are called on themselves. When the check fails, the error ends in MissingRequiredProperties<…>:

The 'this' context of type 'InternalBuilderStep<UserBuilder, User, "id">' is not assignable to method's 'this' of type 'MissingRequiredProperties<"name" | "role">'.

Previously the error was a four-line chain that printed the builder's full brand type up to three times before reaching the missing names. This applies to the auto builders and to the deprecated builders. MissingRequiredProperties is exported.

4. removeRequiredProperty(key)

This is the counterpart of removeOptionalProperty. The main use is building an invalid object for a negative test:

const withoutName = UserBuilder.from(validUser).removeRequiredProperty("name").buildUnsafe();
  • It accepts required keys only.
  • The returned builder carries the exported RemovedRequiredProperties<K> marker, which keeps build() and the other checked variants unavailable, even if the property is set again later.
  • Build the incomplete object with buildUnsafe(), buildPartial() or buildWithoutCompileTimeValidation(). The last one still runs the runtime checks.

5. Deprecate helper types superseded by the auto builders

All of these are still exported and still work. Each @deprecated tag names its replacement:

Deprecated Replacement
BuilderStep / ClassBuilderStep BuilderWith<this, "key", YourBuilder> / ClassBuilderWith
BuilderPreset / ClassBuilderPreset BuilderWith<YourBuilder, "key"> / ClassBuilderWith
BuilderComposer / ClassBuilderComposer BuilderComposerFromFactory / ClassBuilderComposerFromFactory
BuildGate / ClassBuildGate BuilderWith<YourBuilder> / ClassBuilderWith<YourBuilder>
RequiredFieldsTemplate RequiredFieldsRecord<T>, or ReadonlyArray<Path<T>> for nested paths

Generated setters now return a new, non-deprecated InternalBuilderStep / InternalClassBuilderStep. Both are exported, so consumers' declaration emit can name them. It is the same type under a new name, so hovers and the build error never name a deprecated type. This follows the pattern 1.6.0 used for CeriosBrand → InternalBuilderBrand.

I checked two cases where a replacement might not fit:

  • Generic-key methods: BuilderWith<this, K, UserBuilder> works with a generic K.
  • Generic BuildGate: generic code constrained with it never could call build(), so deprecating it loses nothing.

Docs

  • MIGRATION.md
    • New section 10 with a before/after table for every deprecated helper type, including the 1.6.0 ones, which were documented nowhere.
    • A BuilderInit example.
    • A note that a static requiredTemplate / static requiredDataProperties declared on your own subclass can't be flagged by TypeScript or lint, with a grep to find them.
  • README.md
    • Build-error example updated.
    • "Deprecated, still exported" list under Helper types.
    • ClassConstructor entry corrected.
    • BuilderInit shown in forms that actually compile. The previous one-argument BuilderInit<T> did not compile.
  • JSDoc
    • Deprecation messages that pointed at now-deprecated types point at BuilderWith instead.
    • BuilderWith no longer explains itself through setNestedProperty.

Compatibility

  • Reserved name (can break compilation): removeRequiredProperty joins the reserved builder names. A property literally named removeRequiredProperty is now set with removeRequiredPropertyProp.
  • Hover and error text: setter results now print as InternalBuilderStep<…> instead of BuilderStep<…>. The type is identical.
  • Lint: projects with a no-deprecated rule get a warning wherever they name one of the nine deprecated types. Inferred types, hovers and the recommended helpers never warn.
  • Runtime: no change other than the new method.

Verification

  • npm run compile, npm test (645 passing), npm run lint, npm run format:check, npm run build and npm run check-exports all pass.
  • dist/index.d.mts has 17 @deprecated tags (was 8) and exports InternalBuilderStep / InternalClassBuilderStep.
  • Consumer check (a scratch file outside tests/, since tests/** turns no-deprecated off):
    • all nine deprecated names are reported with their replacement message;
    • a too-early build() names InternalBuilderStep.
  • New tests:
    • removeRequiredProperty on all four builders;
    • the build-error shape;
    • the BuilderWith / ClassBuilderWith third argument;
    • the generated setter shape;
    • the export surface: deprecated aliases pinned to their replacements, and setters returning InternalBuilderStep.

Left for the next major

  • Remove the deprecated aliases together with CeriosBuilder / CeriosClassBuilder.
  • Remove the legacy positional-constructor branches in both auto runtimes. Nothing in the library reaches them any more; they're kept for untyped callers and now commented as such.
  • Drop requiredTemplate from AutoBuilderConstructor. CeriosAutoBuilder<T>() returns one shared runtime class for every T, so assigning the static there leaks into every auto builder. The class builder already dropped requiredDataProperties for the same reason.

Housekeeping: .gitignore now ignores *.tgz package archives.

🤖 Generated with Claude Code

…operty removal

- Added optional third type argument to `BuilderWith` and `ClassBuilderWith` for property name autocompletion in instance methods.
- Improved compile error messages for `build()` and related methods to clearly indicate missing required properties.
- Introduced `removeRequiredProperty(key)` method to auto builders and deprecated builders, allowing removal of required properties while returning a new builder.
- Deprecated several helper types that are now redundant due to the new builder functionalities, with clear migration paths provided.
- Updated setter methods to link directly to their corresponding properties, enhancing navigation and documentation visibility in IDEs.
@vethman vethman self-assigned this Oct 5, 2026
@vethman
vethman merged commit 1687ac0 into main Oct 6, 2026
1 of 2 checks passed
@vethman
vethman deleted the feature/ide-dx-improvements branch October 6, 2026 08:32
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