Skip to content

feat(declarations): materialize Effect schema model facades at .d.ts emit#2

Closed
patroza wants to merge 1 commit into
release-6.0from
r/dts-emit-schema-facade
Closed

feat(declarations): materialize Effect schema model facades at .d.ts emit#2
patroza wants to merge 1 commit into
release-6.0from
r/dts-emit-schema-facade

Conversation

@patroza

@patroza patroza commented Jun 21, 2026

Copy link
Copy Markdown
Member

Moves the native model codegen static schema types out of source and into the declaration emitter, so codegen can go back to text-only and the compiler materializes the full shape at .d.ts emit. Based on v6.0.3 (release-6.0) to match the TypeScript version macs/scanner builds with.

What it does (standard struct models)

  • Expands interface Encoded extends S.StructNestedEncoded<typeof X> → literal interface Encoded { … } (nested models referenced by name, e.g. Address.Encoded).
  • Synthesizes interface X (Type) + namespace X { Make; DecodingServices; EncodingServices } from the class schema.
  • Rewrites the emitted X_base
    S.OpaqueFacade<X, X.Encoded, X.Make, X.DecodingServices, X.EncodingServices, {}> intersected with preserved statics (fields/mapFields/to/from/copy) — dropping the expensive S.Struct<{ …full schema… }> from the base (the dominant instantiation lever).

Lives purely in declaration-emit + checker/node-builder plumbing (new EmitResolver methods) so it can be carried as a patch onto the effect-app TypeScript fork.

Gotchas baked in

  • NodeBuilderFlags.UseFullyQualifiedType on the materialization calls — without it nested refs collapse to a bare Encoded (1,412 errors).
  • Detect the model from the base type's first type arg (the _base const has a generated, unreadable name).
  • Gate on the constructor being S.{Opaque,OpaqueClass,OpaqueFacade,Class,TaggedClass} (else non-schema classes like Context.Service get a bogus facade).
  • Detect model namespaces by the presence of an interface Encoded, not its heritage — the namespace pass strips the StructNestedEncoded heritage before this source-file pass runs (this previously silently disabled the base rewrite).

Validation (macs/scanner, tsc 6.0.3, registry, --force, 0 errors)

build aggregate instantiations
no-static main + stock tsc 16,644,980
no-static main + this compiler 13,108,430
static-native branch + stock tsc (target) 13,061,157

~98.7% of the in-source static benefit captured, soundly. Emitted .d.ts is structurally equal to the native (in-source) codegen.

Known WIP (not in this PR)

Bare S.Opaque<X> models (no source Encoded namespace) are left as-is. Synthesizing their full namespace from the class is the next step; complex class-body models still error.

Context + measurements: macs-holding/scanner#1597.

🤖 Generated with Claude Code

…emit

Move the native model codegen's static schema types out of source and into the
declaration emitter. For standard struct models the emitter now:

- expands `interface Encoded extends S.StructNestedEncoded<typeof X>` into a
  literal `interface Encoded { ... }` (nested models referenced by name)
- synthesizes `interface X` (Type) and `namespace X { Make; DecodingServices;
  EncodingServices }` from the class schema
- rewrites the emitted `X_base` to
  `S.OpaqueFacade<X, X.Encoded, X.Make, X.DecodingServices, X.EncodingServices, {}>`
  intersected with the preserved statics (fields/mapFields/to/from/copy),
  dropping the expensive `S.Struct<{ ...full schema... }>` from the base

Lives purely in declaration emit + checker/node-builder plumbing
(new EmitResolver methods) so it can be carried as a patch onto Effect's
TypeScript compiler fork.

Key correctness points: qualify nested refs with UseFullyQualifiedType; detect
the model from the base type's first type argument (the `_base` const has a
generated name); gate on the `S.{Opaque,OpaqueClass,OpaqueFacade,Class,
TaggedClass}` constructor; detect model namespaces by the presence of an
`interface Encoded` (path-1 strips the heritage before this pass runs).

Validated against macs/scanner (tsc 6.0.3, registry, --force, 0 errors):
no-static main + stock tsc = 16,644,980 instantiations; + this compiler =
13,108,430; static-native branch + stock tsc = 13,061,157 (~98.7% of the
in-source benefit captured).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@patroza patroza closed this Jun 22, 2026
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.

1 participant